)]}'
{"version": 3, "sources": ["/web/static/lib/luxon/luxon.js", "/web/static/src/polyfills/object.js", "/web/static/src/polyfills/array.js", "/web/static/src/polyfills/set.js", "/website_forum/static/src/interactions/loader_loading.js", "/web/static/lib/owl/owl.js", "/web/static/lib/owl/odoo_module.js", "/web/static/lib/jquery/jquery.js", "/web/static/lib/popper/popper.js", "/web/static/lib/bootstrap/js/dist/util/index.js", "/web/static/lib/bootstrap/js/dist/dom/data.js", "/web/static/lib/bootstrap/js/dist/dom/event-handler.js", "/web/static/lib/bootstrap/js/dist/dom/manipulator.js", "/web/static/lib/bootstrap/js/dist/dom/selector-engine.js", "/web/static/lib/bootstrap/js/dist/util/config.js", "/web/static/lib/bootstrap/js/dist/util/component-functions.js", "/web/static/lib/bootstrap/js/dist/util/backdrop.js", "/web/static/lib/bootstrap/js/dist/util/focustrap.js", "/web/static/lib/bootstrap/js/dist/util/sanitizer.js", "/web/static/lib/bootstrap/js/dist/util/scrollbar.js", "/web/static/lib/bootstrap/js/dist/util/swipe.js", "/web/static/lib/bootstrap/js/dist/util/template-factory.js", "/web/static/lib/bootstrap/js/dist/base-component.js", "/web/static/lib/bootstrap/js/dist/alert.js", "/web/static/lib/bootstrap/js/dist/button.js", "/web/static/lib/bootstrap/js/dist/carousel.js", "/web/static/lib/bootstrap/js/dist/collapse.js", "/web/static/lib/bootstrap/js/dist/dropdown.js", "/web/static/lib/bootstrap/js/dist/modal.js", "/web/static/lib/bootstrap/js/dist/offcanvas.js", "/web/static/lib/bootstrap/js/dist/tooltip.js", "/web/static/lib/bootstrap/js/dist/popover.js", "/web/static/lib/bootstrap/js/dist/scrollspy.js", "/web/static/lib/bootstrap/js/dist/tab.js", "/web/static/lib/bootstrap/js/dist/toast.js", "/web/static/src/libs/bootstrap.js", "/web/static/src/legacy/js/libs/jquery.js", "/web/static/src/legacy/js/core/class.js", "/web/static/src/env.js", "/web/static/src/core/action_swiper/action_swiper.js", "/web/static/src/core/anchor_scroll_prevention.js", "/web/static/src/core/assets.js", "/web/static/src/core/autocomplete/autocomplete.js", "/web/static/src/core/barcode/ZXingBarcodeDetector.js", "/web/static/src/core/barcode/barcode_dialog.js", "/web/static/src/core/barcode/barcode_video_scanner.js", "/web/static/src/core/barcode/crop_overlay.js", "/web/static/src/core/bottom_sheet/bottom_sheet.js", "/web/static/src/core/bottom_sheet/bottom_sheet_service.js", "/web/static/src/core/browser/browser.js", "/web/static/src/core/browser/feature_detection.js", "/web/static/src/core/browser/router.js", "/web/static/src/core/browser/title_service.js", "/web/static/src/core/checkbox/checkbox.js", "/web/static/src/core/code_editor/code_editor.js", "/web/static/src/core/color_picker/color_picker.js", "/web/static/src/core/color_picker/custom_color_picker/custom_color_picker.js", "/web/static/src/core/color_picker/tabs/color_picker_custom_tab.js", "/web/static/src/core/color_picker/tabs/color_picker_solid_tab.js", "/web/static/src/core/colorlist/colorlist.js", "/web/static/src/core/colors/colors.js", "/web/static/src/core/confirmation_dialog/confirmation_dialog.js", "/web/static/src/core/context.js", "/web/static/src/core/copy_button/copy_button.js", "/web/static/src/core/currency.js", "/web/static/src/core/datetime/datetime_input.js", "/web/static/src/core/datetime/datetime_picker.js", "/web/static/src/core/datetime/datetime_picker_hook.js", "/web/static/src/core/datetime/datetime_picker_popover.js", "/web/static/src/core/datetime/datetimepicker_service.js", "/web/static/src/core/debug/debug_context.js", "/web/static/src/core/debug/debug_menu_basic.js", "/web/static/src/core/debug/debug_menu_items.js", "/web/static/src/core/debug/debug_providers.js", "/web/static/src/core/debug/debug_utils.js", "/web/static/src/core/dialog/dialog.js", "/web/static/src/core/dialog/dialog_service.js", "/web/static/src/core/domain.js", "/web/static/src/core/domain_selector/domain_selector.js", "/web/static/src/core/domain_selector/domain_selector_operator_editor.js", "/web/static/src/core/domain_selector/utils.js", "/web/static/src/core/domain_selector_dialog/domain_selector_dialog.js", "/web/static/src/core/dropdown/_behaviours/dropdown_group_hook.js", "/web/static/src/core/dropdown/_behaviours/dropdown_nesting.js", "/web/static/src/core/dropdown/_behaviours/dropdown_popover.js", "/web/static/src/core/dropdown/accordion_item.js", "/web/static/src/core/dropdown/checkbox_item.js", "/web/static/src/core/dropdown/dropdown.js", "/web/static/src/core/dropdown/dropdown_group.js", "/web/static/src/core/dropdown/dropdown_hooks.js", "/web/static/src/core/dropdown/dropdown_item.js", "/web/static/src/core/dropzone/dropzone.js", "/web/static/src/core/dropzone/dropzone_hook.js", "/web/static/src/core/effects/effect_service.js", "/web/static/src/core/effects/rainbow_man.js", "/web/static/src/core/emoji_picker/emoji_picker.js", "/web/static/src/core/emoji_picker/frequent_emoji_service.js", "/web/static/src/core/ensure_jquery.js", "/web/static/src/core/errors/error_dialogs.js", "/web/static/src/core/errors/error_handlers.js", "/web/static/src/core/errors/error_service.js", "/web/static/src/core/errors/error_utils.js", "/web/static/src/core/errors/scss_error_dialog.js", "/web/static/src/core/expression_editor/expression_editor.js", "/web/static/src/core/expression_editor/expression_editor_operator_editor.js", "/web/static/src/core/expression_editor_dialog/expression_editor_dialog.js", "/web/static/src/core/field_service.js", "/web/static/src/core/file_input/file_input.js", "/web/static/src/core/file_upload/file_upload_progress_bar.js", "/web/static/src/core/file_upload/file_upload_progress_container.js", "/web/static/src/core/file_upload/file_upload_progress_record.js", "/web/static/src/core/file_upload/file_upload_service.js", "/web/static/src/core/file_viewer/file_model.js", "/web/static/src/core/file_viewer/file_viewer.js", "/web/static/src/core/file_viewer/file_viewer_hook.js", "/web/static/src/core/hotkeys/hotkey_hook.js", "/web/static/src/core/hotkeys/hotkey_service.js", "/web/static/src/core/install_scoped_app/install_scoped_app.js", "/web/static/src/core/ir_ui_view_code_editor/code_editor.js", "/web/static/src/core/l10n/dates.js", "/web/static/src/core/l10n/localization.js", "/web/static/src/core/l10n/localization_service.js", "/web/static/src/core/l10n/time.js", "/web/static/src/core/l10n/translation.js", "/web/static/src/core/l10n/utils.js", "/web/static/src/core/l10n/utils/format_list.js", "/web/static/src/core/l10n/utils/locales.js", "/web/static/src/core/l10n/utils/normalize.js", "/web/static/src/core/macro.js", "/web/static/src/core/main_components_container.js", "/web/static/src/core/model_field_selector/model_field_selector.js", "/web/static/src/core/model_field_selector/model_field_selector_popover.js", "/web/static/src/core/model_selector/model_selector.js", "/web/static/src/core/name_service.js", "/web/static/src/core/navigation/navigation.js", "/web/static/src/core/network/download.js", "/web/static/src/core/network/http_service.js", "/web/static/src/core/network/rpc.js", "/web/static/src/core/network/rpc_cache.js", "/web/static/src/core/notebook/notebook.js", "/web/static/src/core/notifications/notification.js", "/web/static/src/core/notifications/notification_container.js", "/web/static/src/core/notifications/notification_service.js", "/web/static/src/core/orm_service.js", "/web/static/src/core/overlay/overlay_container.js", "/web/static/src/core/overlay/overlay_service.js", "/web/static/src/core/pager/pager.js", "/web/static/src/core/pager/pager_indicator.js", "/web/static/src/core/popover/popover.js", "/web/static/src/core/popover/popover_hook.js", "/web/static/src/core/popover/popover_service.js", "/web/static/src/core/position/position_hook.js", "/web/static/src/core/position/utils.js", "/web/static/src/core/pwa/install_prompt.js", "/web/static/src/core/pwa/pwa_service.js", "/web/static/src/core/py_js/py.js", "/web/static/src/core/py_js/py_builtin.js", "/web/static/src/core/py_js/py_date.js", "/web/static/src/core/py_js/py_interpreter.js", "/web/static/src/core/py_js/py_parser.js", "/web/static/src/core/py_js/py_tokenizer.js", "/web/static/src/core/py_js/py_utils.js", "/web/static/src/core/record_selectors/multi_record_selector.js", "/web/static/src/core/record_selectors/record_autocomplete.js", "/web/static/src/core/record_selectors/record_selector.js", "/web/static/src/core/record_selectors/tag_navigation_hook.js", "/web/static/src/core/registry.js", "/web/static/src/core/registry_hook.js", "/web/static/src/core/resizable_panel/resizable_panel.js", "/web/static/src/core/select_menu/select_menu.js", "/web/static/src/core/signature/name_and_signature.js", "/web/static/src/core/signature/signature_dialog.js", "/web/static/src/core/tags_list/tags_list.js", "/web/static/src/core/template_inheritance.js", "/web/static/src/core/templates.js", "/web/static/src/core/time_picker/time_picker.js", "/web/static/src/core/tooltip/tooltip.js", "/web/static/src/core/tooltip/tooltip_hook.js", "/web/static/src/core/tooltip/tooltip_service.js", "/web/static/src/core/transition.js", "/web/static/src/core/tree_editor/ast_utils.js", "/web/static/src/core/tree_editor/condition_tree.js", "/web/static/src/core/tree_editor/construct_domain_from_tree.js", "/web/static/src/core/tree_editor/construct_expression_from_tree.js", "/web/static/src/core/tree_editor/construct_tree_from_domain.js", "/web/static/src/core/tree_editor/construct_tree_from_expression.js", "/web/static/src/core/tree_editor/domain_contains_expressions.js", "/web/static/src/core/tree_editor/domain_from_tree.js", "/web/static/src/core/tree_editor/expression_from_tree.js", "/web/static/src/core/tree_editor/operators.js", "/web/static/src/core/tree_editor/tree_editor.js", "/web/static/src/core/tree_editor/tree_editor_autocomplete.js", "/web/static/src/core/tree_editor/tree_editor_components.js", "/web/static/src/core/tree_editor/tree_editor_operator_editor.js", "/web/static/src/core/tree_editor/tree_editor_value_editors.js", "/web/static/src/core/tree_editor/tree_from_domain.js", "/web/static/src/core/tree_editor/tree_from_expression.js", "/web/static/src/core/tree_editor/tree_processor.js", "/web/static/src/core/tree_editor/utils.js", "/web/static/src/core/tree_editor/virtual_operators.js", "/web/static/src/core/ui/block_ui.js", "/web/static/src/core/ui/ui_service.js", "/web/static/src/core/user.js", "/web/static/src/core/user_switch/user_switch.js", "/web/static/src/core/utils/arrays.js", "/web/static/src/core/utils/autoresize.js", "/web/static/src/core/utils/binary.js", "/web/static/src/core/utils/cache.js", "/web/static/src/core/utils/classname.js", "/web/static/src/core/utils/colors.js", "/web/static/src/core/utils/components.js", "/web/static/src/core/utils/concurrency.js", "/web/static/src/core/utils/draggable.js", "/web/static/src/core/utils/draggable_hook_builder.js", "/web/static/src/core/utils/draggable_hook_builder_owl.js", "/web/static/src/core/utils/dvu.js", "/web/static/src/core/utils/files.js", "/web/static/src/core/utils/functions.js", "/web/static/src/core/utils/hooks.js", "/web/static/src/core/utils/html.js", "/web/static/src/core/utils/indexed_db.js", "/web/static/src/core/utils/misc.js", "/web/static/src/core/utils/nested_sortable.js", "/web/static/src/core/utils/numbers.js", "/web/static/src/core/utils/objects.js", "/web/static/src/core/utils/patch.js", "/web/static/src/core/utils/pdfjs.js", "/web/static/src/core/utils/reactive.js", "/web/static/src/core/utils/render.js", "/web/static/src/core/utils/scrolling.js", "/web/static/src/core/utils/search.js", "/web/static/src/core/utils/sortable.js", "/web/static/src/core/utils/sortable_owl.js", "/web/static/src/core/utils/sortable_service.js", "/web/static/src/core/utils/strings.js", "/web/static/src/core/utils/timing.js", "/web/static/src/core/utils/urls.js", "/web/static/src/core/utils/xml.js", "/web/static/src/core/virtual_grid_hook.js", "/web/static/src/core/commands/default_providers.js", "/web/static/src/core/commands/command_palette.js", "/web/static/src/public/caps_lock_warning.js", "/web/static/src/public/colibri.js", "/web/static/src/public/datetime_picker.js", "/web/static/src/public/error_notifications.js", "/web/static/src/public/interaction.js", "/web/static/src/public/interaction_service.js", "/web/static/src/public/login.js", "/web/static/src/public/public_component_interaction.js", "/web/static/src/public/show_password.js", "/web/static/src/public/utils.js", "/web/static/src/legacy/js/public/public_root.js", "/website/static/src/js/content/website_root_instance.js", "/web/static/src/legacy/js/public/public_widget.js", "/bus/static/src/bus_parameters_service.js", "/bus/static/src/legacy_multi_tab_service.js", "/bus/static/src/misc.js", "/bus/static/src/multi_tab_fallback_service.js", "/bus/static/src/multi_tab_service.js", "/bus/static/src/multi_tab_shared_worker_service.js", "/bus/static/src/outdated_page_watcher_service.js", "/bus/static/src/services/bus_monitoring_service.js", "/bus/static/src/services/bus_service.js", "/bus/static/src/services/presence_service.js", "/bus/static/src/services/worker_service.js", "/bus/static/src/workers/base_worker.js", "/bus/static/src/workers/bus_worker_utils.js", "/bus/static/src/workers/election_worker.js", "/bus/static/src/workers/websocket_worker.js", "/web_tour/static/src/js/tour_pointer/tour_pointer.js", "/web_tour/static/src/js/tour_pointer/tour_pointer_state.js", "/web_tour/static/src/js/utils/tour_utils.js", "/web_tour/static/src/js/tour_state.js", "/web_tour/static/src/js/tour_service.js", "/web_tour/static/src/js/tour_recorder/tour_recorder_state.js", "/web_tour/static/src/tour_utils.js", "/html_editor/static/src/components/switch/switch.js", "/html_editor/static/src/main/media/media_dialog/document_selector.js", "/html_editor/static/src/main/media/media_dialog/file_selector.js", "/html_editor/static/src/main/media/media_dialog/icon_selector.js", "/html_editor/static/src/main/media/media_dialog/image_selector.js", "/html_editor/static/src/main/media/media_dialog/media_dialog.js", "/html_editor/static/src/main/media/media_dialog/search_media.js", "/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.js", "/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_service.js", "/html_editor/static/src/main/media/media_dialog/video_selector.js", "/website/static/src/components/media_dialog/image_selector.js", "/web_unsplash/static/src/media_dialog/image_selector_patch.js", "/web_unsplash/static/src/media_dialog/media_dialog_patch.js", "/web_unsplash/static/src/unsplash_credentials/unsplash_credentials.js", "/web_unsplash/static/src/unsplash_error/unsplash_error.js", "/web_unsplash/static/src/unsplash_service.js", "/html_editor/static/src/components/html_viewer/html_viewer.js", "/html_editor/static/src/local_overlay_container.js", "/html_editor/static/src/position_hook.js", "/html_editor/static/src/html_migrations/html_migrations_utils.js", "/html_editor/static/src/html_migrations/html_upgrade_manager.js", "/html_editor/static/src/html_migrations/manifest.js", "/html_editor/static/src/html_migrations/migration-1.1.js", "/html_editor/static/src/html_migrations/migration-1.2.js", "/html_editor/static/src/others/embedded_component_utils.js", "/html_editor/static/src/others/embedded_components/core/embedded_component_toolbar/embedded_component_toolbar.js", "/html_editor/static/src/others/embedded_components/core/file/readonly_file.js", "/html_editor/static/src/others/embedded_components/core/file/state_file_model.js", "/html_editor/static/src/others/embedded_components/core/syntax_highlighting/readonly_syntax_highlighting.js", "/html_editor/static/src/others/embedded_components/core/syntax_highlighting/syntax_highlighting_utils.js", "/html_editor/static/src/others/embedded_components/core/table_of_content/table_of_content.js", "/html_editor/static/src/others/embedded_components/core/table_of_content/table_of_content_manager.js", "/html_editor/static/src/others/embedded_components/core/toggle_block/toggle_block.js", "/html_editor/static/src/others/embedded_components/core/video/readonly_video.js", "/html_editor/static/src/utils/base_container.js", "/html_editor/static/src/utils/blocks.js", "/html_editor/static/src/utils/clipboard.js", "/html_editor/static/src/utils/color.js", "/html_editor/static/src/utils/content_types.js", "/html_editor/static/src/utils/dom.js", "/html_editor/static/src/utils/dom_info.js", "/html_editor/static/src/utils/dom_state.js", "/html_editor/static/src/utils/dom_traversal.js", "/html_editor/static/src/utils/drag_and_drop.js", "/html_editor/static/src/utils/fonts.js", "/html_editor/static/src/utils/formatting.js", "/html_editor/static/src/utils/functions.js", "/html_editor/static/src/utils/html.js", "/html_editor/static/src/utils/image.js", "/html_editor/static/src/utils/image_processing.js", "/html_editor/static/src/utils/perspective_utils.js", "/html_editor/static/src/utils/position.js", "/html_editor/static/src/utils/regex.js", "/html_editor/static/src/utils/resource.js", "/html_editor/static/src/utils/sanitize.js", "/html_editor/static/src/utils/selection.js", "/html_editor/static/src/utils/table.js", "/html_editor/static/src/utils/tracking.js", "/html_editor/static/src/utils/url.js", "/html_editor/static/src/public/embedded_components/embedded_component_interaction.js", "/html_editor/static/src/public/embedding_sets.js", "/html_editor/static/src/public/html_migrations/html_migrations_interaction.js", "/mail/static/src/utils/common/format.js", "/auth_signup/static/src/interactions/signup.js", "/portal/static/src/interactions/address.js", "/portal/static/src/interactions/address_card.js", "/portal/static/src/interactions/portal_composer.js", "/portal/static/src/interactions/portal_details.js", "/portal/static/src/interactions/portal_home_counters.js", "/portal/static/src/interactions/portal_search_panel.js", "/portal/static/src/interactions/portal_security.js", "/portal/static/src/interactions/sidebar.js", "/portal/static/src/js/components/input_confirmation_dialog/input_confirmation_dialog.js", "/portal/static/src/signature_form/signature_form.js", "/portal/static/src/chatter/boot/boot_service.js", "/account/static/src/interactions/account_portal.js", "/account/static/src/interactions/account_sidebar.js", "/account/static/src/components/tests_shared_js_python/tests_shared_js_python.js", "/account/static/src/helpers/account_tax.js", "/payment/static/src/interactions/express_checkout.js", "/payment/static/src/interactions/payment_button.js", "/payment/static/src/interactions/payment_form.js", "/payment/static/src/interactions/post_processing.js", "/account_payment/static/src/interactions/payment_form.js", "/account_payment/static/src/interactions/portal_invoice_page_payment.js", "/account_payment/static/src/interactions/portal_my_invoices_payment_list.js", "/sale/static/src/interactions/portal_prepayment.js", "/sale/static/src/interactions/sale_portal.js", "/sale/static/src/interactions/sale_sidebar.js", "/sale_management/static/src/interactions/sale_update_line_button.js", "/google_recaptcha/static/src/js/recaptcha.js", "/google_recaptcha/static/src/interactions/recaptcha_form.js", "/html_builder/static/src/utils/scrolling.js", "/website/static/src/interactions/_example.js", "/website/static/src/interactions/anchor_slide.js", "/website/static/src/interactions/animate_overflow.js", "/website/static/src/interactions/animation.js", "/website/static/src/interactions/bottom_fixed_element.js", "/website/static/src/interactions/carousel/carousel_bootstrap_upgrade_fix.js", "/website/static/src/interactions/carousel/carousel_slider.js", "/website/static/src/interactions/carousel/carousel_slider.preview.js", "/website/static/src/interactions/cookies/cookies_approval.js", "/website/static/src/interactions/cookies/cookies_bar.js", "/website/static/src/interactions/cookies/cookies_toggle.js", "/website/static/src/interactions/cookies/cookies_warning.js", "/website/static/src/interactions/dropdown/hoverable_dropdown.js", "/website/static/src/interactions/dropdown/mega_menu_dropdown.js", "/website/static/src/interactions/footer_slideout.js", "/website/static/src/interactions/full_screen_height.js", "/website/static/src/interactions/header/base_header.js", "/website/static/src/interactions/header/base_header_special.js", "/website/static/src/interactions/header/header_disappears.js", "/website/static/src/interactions/header/header_fade_out.js", "/website/static/src/interactions/header/header_fixed.js", "/website/static/src/interactions/header/header_standard.js", "/website/static/src/interactions/header/header_top.js", "/website/static/src/interactions/image_lazy_loading.js", "/website/static/src/interactions/image_shape_hover_effect.js", "/website/static/src/interactions/listing_layout.js", "/website/static/src/interactions/parallax/parallax.js", "/website/static/src/interactions/parallax/parallax.preview.js", "/website/static/src/interactions/plausible_push.js", "/website/static/src/interactions/popup/no_backdrop_popup.js", "/website/static/src/interactions/popup/popup.js", "/website/static/src/interactions/popup/shared_popup.js", "/website/static/src/interactions/post_link.js", "/website/static/src/interactions/scroll_button.js", "/website/static/src/interactions/search_modal.js", "/website/static/src/interactions/text_highlights.js", "/website/static/src/interactions/text_highlights.preview.js", "/website/static/src/interactions/video/background_video.js", "/website/static/src/interactions/video/media_video.js", "/website/static/src/interactions/zoomed_background_shape.js", "/website/static/src/core/component_interaction_edit.js", "/website/static/src/core/errors/beforeunload_error_handler.js", "/website/static/src/core/website_cookies_service.js", "/website/static/src/core/website_map_service.js", "/website/static/src/core/website_menus_service.js", "/website/static/src/core/website_page_service.js", "/website/static/src/utils/images.js", "/website/static/src/utils/videos.js", "/website/static/src/snippets/s_announcement_scroll/announcement_scroll.js", "/website/static/src/snippets/s_chart/chart.js", "/website/static/src/snippets/s_countdown/countdown.js", "/website/static/src/snippets/s_dynamic_snippet/dynamic_snippet.js", "/website/static/src/snippets/s_dynamic_snippet_carousel/dynamic_snippet_carousel.js", "/website/static/src/snippets/s_embed_code/embed_code.js", "/website/static/src/snippets/s_facebook_page/facebook_page.js", "/website/static/src/snippets/s_faq_horizontal/faq_horizontal.js", "/website/static/src/snippets/s_floating_blocks/floating_blocks.js", "/website/static/src/snippets/s_google_map/google_map.js", "/website/static/src/snippets/s_image_gallery/gallery.js", "/website/static/src/snippets/s_image_gallery/gallery_slider.js", "/website/static/src/snippets/s_instagram_page/instagram_page.js", "/website/static/src/snippets/s_map/map.js", "/website/static/src/snippets/s_searchbar/search_bar.js", "/website/static/src/snippets/s_searchbar/search_bar_results.js", "/website/static/src/snippets/s_share/share.js", "/website/static/src/snippets/s_table_of_content/table_of_content.js", "/website/static/src/snippets/s_website_form/form.js", "/website/static/src/libs/zoomodoo/zoomodoo.js", "/website/static/src/libs/bootstrap/bootstrap.js", "/website/static/src/js/utils.js", "/website/static/src/components/autocomplete_with_pages/autocomplete_with_pages.js", "/website/static/src/components/autocomplete_with_pages/url_autocomplete.js", "/website/static/src/js/tours/tour_utils.js", "/website/static/src/js/content/website_root.js", "/website/static/src/js/content/compatibility.js", "/website/static/src/js/content/snippets.animation.js", "/website/static/src/js/user_custom_javascript.js", "/website/static/src/js/http_cookie.js", "/website/static/src/js/text_processing.js", "/website/static/src/js/highlight_utils.js", "/website/static/src/components/user_switch.js", "/website_payment/static/src/interactions/donation_form.js", "/website_payment/static/src/interactions/payment_form.js", "/website_payment/static/src/snippets/s_donation/donation_snippet.js", "/website_payment/static/src/snippets/s_supported_payment_methods/supported_payment_methods.js", "/website_mail/static/src/interactions/follow.js", "/portal_rating/static/src/interactions/portal_composer.js", "/portal_rating/static/src/interactions/portal_rating_composer.js", "/payment_custom/static/src/interactions/post_processing.js", "/delivery/static/src/interactions/payment_form.js", "/delivery/static/src/js/location_selector/location/location.js", "/delivery/static/src/js/location_selector/location_list/location_list.js", "/delivery/static/src/js/location_selector/location_schedule/location_schedule.js", "/delivery/static/src/js/location_selector/location_selector_dialog/location_selector_dialog.js", "/delivery/static/src/js/location_selector/map/map.js", "/delivery/static/src/js/location_selector/map_container/map_container.js", "/website_sale/static/src/interactions/address.js", "/website_sale/static/src/interactions/carousel_product.js", "/website_sale/static/src/interactions/cart_line.js", "/website_sale/static/src/interactions/cart_suggestion.js", "/website_sale/static/src/interactions/checkout.js", "/website_sale/static/src/interactions/extra_info_form.js", "/website_sale/static/src/interactions/off_canvas.js", "/website_sale/static/src/interactions/payment_button.js", "/website_sale/static/src/interactions/payment_form.js", "/website_sale/static/src/interactions/popup.js", "/website_sale/static/src/interactions/price_range.js", "/website_sale/static/src/interactions/product/product_images_grid.js", "/website_sale/static/src/interactions/product/product_variant_preview.js", "/website_sale/static/src/interactions/product/product_variant_preview_hover.js", "/website_sale/static/src/interactions/product_accordion.js", "/website_sale/static/src/interactions/product_sticky_col.js", "/website_sale/static/src/interactions/product_tile_secondary_image.js", "/website_sale/static/src/interactions/quick_reorder.js", "/website_sale/static/src/interactions/recently_viewed_products.js", "/website_sale/static/src/interactions/reorder.js", "/website_sale/static/src/interactions/search_modal.js", "/website_sale/static/src/interactions/sticky_object.js", "/website_sale/static/src/interactions/terms_and_conditions_checkbox.js", "/website_sale/static/src/interactions/tracking.js", "/website_sale_stock/static/src/js/variant_mixin.js", "/website_sale_stock_wishlist/static/src/js/variant_mixin.js", "/website_sale/static/src/interactions/website_sale.js", "/website_sale/static/src/snippets/s_add_to_cart/add_to_cart_snippet.js", "/website_sale/static/src/snippets/s_dynamic_snippet_categories/dynamic_snippet_category.js", "/website_sale/static/src/snippets/s_dynamic_snippet_products/carousel_product_card.js", "/website_sale/static/src/snippets/s_dynamic_snippet_products/dynamic_snippet_products.js", "/website_sale/static/src/js/tours/tour_utils.js", "/website_sale/static/src/js/cart_service.js", "/website_sale/static/src/js/variant_mixin.js", "/website_sale/static/src/js/website_sale_utils.js", "/website/static/lib/multirange/multirange_custom.js", "/website/static/src/interactions/multirange_input.js", "/website_sale/static/src/js/components/website_sale_image_viewer.js", "/website_sale/static/src/js/notification/add_to_cart_notification/add_to_cart_notification.js", "/website_sale/static/src/js/notification/cart_notification/cart_notification.js", "/website_sale/static/src/js/notification/warning_notification/warning_notification.js", "/website_sale/static/src/js/notification/notification_service.js", "/sale/static/src/js/badge_extra_price/badge_extra_price.js", "/sale/static/src/js/combo_configurator_dialog/combo_configurator_dialog.js", "/sale/static/src/js/models/product_combo.js", "/sale/static/src/js/models/product_combo_item.js", "/sale/static/src/js/models/product_product.js", "/sale/static/src/js/models/product_template_attribute_line.js", "/sale/static/src/js/models/product_template_attribute_value.js", "/sale/static/src/js/product/product.js", "/sale/static/src/js/product_card/product_card.js", "/sale/static/src/js/product_configurator_dialog/product_configurator_dialog.js", "/sale/static/src/js/product_list/product_list.js", "/sale/static/src/js/product_template_attribute_line/product_template_attribute_line.js", "/sale/static/src/js/quantity_buttons/quantity_buttons.js", "/sale/static/src/js/sale_utils.js", "/website_sale/static/src/js/combo_configurator_dialog/combo_configurator_dialog.js", "/website_sale/static/src/js/product/product.js", "/website_sale/static/src/js/product_configurator_dialog/product_configurator_dialog.js", "/website_sale/static/src/js/product_list/product_list.js", "/website_sale/static/src/js/product_template_attribute_line/product_template_attribute_line.js", "/website_sale/static/src/js/location_selector/location/location.js", "/website_sale/static/src/js/location_selector/location_schedule/location_schedule.js", "/website_sale/static/src/js/location_selector/location_selector_dialog/location_selector_dialog.js", "/website_sale/static/src/js/location_selector/map_container/map_container.js", "/web_unsplash/static/src/frontend/unsplash_beacon.js", "/account_online_synchronization/static/src/interactions/online_sync_portal.js", "/account_peppol/static/src/interactions/invoice_sending_method.js", "/auth_passkey/static/lib/simplewebauthn.js", "/auth_passkey/static/src/interactions/passkey_login.js", "/auth_passkey_portal/static/src/js/passkeys_portal.js", "/auth_passkey_portal/static/src/js/passkeys_portal_create.js", "/auth_totp_portal/static/src/interactions/revoke_all_trusted_devices.js", "/auth_totp_portal/static/src/interactions/revoke_trusted_device.js", "/auth_totp_portal/static/src/interactions/totp_disable.js", "/auth_totp_portal/static/src/interactions/totp_enable.js", "/saas_trial/static/js/activation_frontend.js", "/payment_stripe/static/src/interactions/express_checkout.js", "/payment_stripe/static/src/interactions/payment_form.js", "/payment_stripe/static/src/interactions/stripe_options.js", "/saas_payment_stripe/static/src/js/payment_form.js", "/saas_payment_stripe/static/src/js/stripe_options.js", "/google_address_autocomplete/static/src/google_places_session.js", "/website_sale_autocomplete/static/src/interactions/address_form.js", "/knowledge/static/src/portal_webclient/router_utils.js", "/knowledge/static/src/components/article_search_dialog/article_search_dialog.js", "/knowledge/static/src/editor/embedded_components/core/article_index/readonly_article_index.js", "/knowledge/static/src/editor/embedded_components/core/clipboard/embedded_clipboard.js", "/knowledge/static/src/editor/embedded_components/core/embedded_view_link/embedded_view_link_style.js", "/knowledge/static/src/editor/embedded_components/core/readonly_foldable_section/readonly_foldable_section.js", "/knowledge/static/src/editor/html_migrations/manifest.js", "/knowledge/static/src/editor/html_migrations/migration-1.0.js", "/knowledge/static/src/editor/html_migrations/migration-2.0.js", "/knowledge/static/src/editor/html_migrations/utils.js", "/website_knowledge/static/src/frontend/editor/embedded_components/embedded_component_interaction.js", "/website_knowledge/static/src/frontend/editor/embedded_components/embedding_sets.js", "/website_knowledge/static/src/frontend/editor/embedded_components/view/view_placeholder.js", "/website_knowledge/static/src/frontend/editor/embedded_components/view_link/public_embedded_view_link.js", "/website_knowledge/static/src/frontend/editor/html_migrations/html_migrations_interaction.js", "/website_knowledge/static/src/frontend/knowledge_public_view/knowledge_public_article.js", "/website_knowledge/static/src/frontend/knowledge_public_view/knowledge_public_view_interaction.js", "/website_knowledge/static/src/frontend/sidebar/knowledge_public_sidebar.js", "/web/static/src/views/fields/file_handler.js", "/website_profile/static/src/components/profile_dialog/profile_dialog.js", "/website_profile/static/src/interactions/next_rank_card.js", "/website_profile/static/src/interactions/profile_editor.js", "/website_profile/static/src/interactions/profile_validation.js", "/web/static/lib/dompurify/DOMpurify.js", "/html_editor/static/src/dropdown_autovisibility_hook.js", "/html_editor/static/src/editor.js", "/html_editor/static/src/plugin.js", "/html_editor/static/src/plugin_sets.js", "/html_editor/static/src/wysiwyg.js", "/html_editor/static/src/components/history_dialog/history_dialog.js", "/html_editor/static/src/core/base_container_plugin.js", "/html_editor/static/src/core/clipboard_plugin.js", "/html_editor/static/src/core/comment_plugin.js", "/html_editor/static/src/core/content_editable_plugin.js", "/html_editor/static/src/core/delete_plugin.js", "/html_editor/static/src/core/dialog_plugin.js", "/html_editor/static/src/core/dom_plugin.js", "/html_editor/static/src/core/editor_version_plugin.js", "/html_editor/static/src/core/format_plugin.js", "/html_editor/static/src/core/history_plugin.js", "/html_editor/static/src/core/input_plugin.js", "/html_editor/static/src/core/line_break_plugin.js", "/html_editor/static/src/core/no_inline_root_plugin.js", "/html_editor/static/src/core/overlay.js", "/html_editor/static/src/core/overlay_plugin.js", "/html_editor/static/src/core/protected_node_plugin.js", "/html_editor/static/src/core/sanitize_plugin.js", "/html_editor/static/src/core/selection_plugin.js", "/html_editor/static/src/core/shortcut_plugin.js", "/html_editor/static/src/core/split_plugin.js", "/html_editor/static/src/core/style_plugin.js", "/html_editor/static/src/core/user_command_plugin.js", "/html_editor/static/src/main/align/align_plugin.js", "/html_editor/static/src/main/align/align_selector.js", "/html_editor/static/src/main/banner_plugin.js", "/html_editor/static/src/main/chatgpt/chatgpt_dialog.js", "/html_editor/static/src/main/chatgpt/chatgpt_translate_dialog.js", "/html_editor/static/src/main/chatgpt/chatgpt_translate_plugin.js", "/html_editor/static/src/main/chatgpt/language_selector.js", "/html_editor/static/src/main/column_plugin.js", "/html_editor/static/src/main/emoji_plugin.js", "/html_editor/static/src/main/feff_plugin.js", "/html_editor/static/src/main/font/color_picker_gradient_tab.js", "/html_editor/static/src/main/font/color_plugin.js", "/html_editor/static/src/main/font/color_selector.js", "/html_editor/static/src/main/font/color_ui_plugin.js", "/html_editor/static/src/main/font/font_family_plugin.js", "/html_editor/static/src/main/font/font_family_selector.js", "/html_editor/static/src/main/font/font_plugin.js", "/html_editor/static/src/main/font/font_selector.js", "/html_editor/static/src/main/font/font_size_selector.js", "/html_editor/static/src/main/font/gradient_picker/gradient_picker.js", "/html_editor/static/src/main/hint_plugin.js", "/html_editor/static/src/main/inline_code.js", "/html_editor/static/src/main/link/command_category.js", "/html_editor/static/src/main/link/link_paste_plugin.js", "/html_editor/static/src/main/link/link_plugin.js", "/html_editor/static/src/main/link/link_popover.js", "/html_editor/static/src/main/link/link_selection_odoo_plugin.js", "/html_editor/static/src/main/link/link_selection_plugin.js", "/html_editor/static/src/main/link/powerbox_url_paste_plugin.js", "/html_editor/static/src/main/link/utils.js", "/html_editor/static/src/main/list/list_plugin.js", "/html_editor/static/src/main/list/list_selector.js", "/html_editor/static/src/main/list/utils.js", "/html_editor/static/src/main/local_overlay_plugin.js", "/html_editor/static/src/main/media/dblclick_image_preview_plugin.js", "/html_editor/static/src/main/media/file_plugin.js", "/html_editor/static/src/main/media/icon_color_plugin.js", "/html_editor/static/src/main/media/icon_plugin.js", "/html_editor/static/src/main/media/image_crop.js", "/html_editor/static/src/main/media/image_crop_plugin.js", "/html_editor/static/src/main/media/image_description.js", "/html_editor/static/src/main/media/image_plugin.js", "/html_editor/static/src/main/media/image_post_process_plugin.js", "/html_editor/static/src/main/media/image_save_plugin.js", "/html_editor/static/src/main/media/image_toolbar_dropdown.js", "/html_editor/static/src/main/media/image_transform_button.js", "/html_editor/static/src/main/media/image_transformation.js", "/html_editor/static/src/main/media/media_plugin.js", "/html_editor/static/src/main/media/video_plugin.js", "/html_editor/static/src/main/movenode_plugin.js", "/html_editor/static/src/main/placeholder_plugin.js", "/html_editor/static/src/main/position_plugin.js", "/html_editor/static/src/main/power_buttons_plugin.js", "/html_editor/static/src/main/powerbox/powerbox.js", "/html_editor/static/src/main/powerbox/powerbox_plugin.js", "/html_editor/static/src/main/powerbox/search_powerbox_plugin.js", "/html_editor/static/src/main/selection_placeholder_plugin.js", "/html_editor/static/src/main/separator_plugin.js", "/html_editor/static/src/main/star_plugin.js", "/html_editor/static/src/main/table/table_align_plugin.js", "/html_editor/static/src/main/table/table_align_selector.js", "/html_editor/static/src/main/table/table_menu.js", "/html_editor/static/src/main/table/table_picker.js", "/html_editor/static/src/main/table/table_plugin.js", "/html_editor/static/src/main/table/table_resize_plugin.js", "/html_editor/static/src/main/table/table_ui_plugin.js", "/html_editor/static/src/main/tabulation_plugin.js", "/html_editor/static/src/main/text_direction_plugin.js", "/html_editor/static/src/main/toolbar/mobile_toolbar.js", "/html_editor/static/src/main/toolbar/toolbar.js", "/html_editor/static/src/main/toolbar/toolbar_plugin.js", "/html_editor/static/src/main/youtube_plugin.js", "/html_editor/static/src/others/collaboration/PeerToPeer.js", "/html_editor/static/src/others/collaboration/collaboration_odoo_plugin.js", "/html_editor/static/src/others/collaboration/collaboration_plugin.js", "/html_editor/static/src/others/collaboration/collaboration_selection_avatar_plugin.js", "/html_editor/static/src/others/collaboration/collaboration_selection_plugin.js", "/html_editor/static/src/others/embedded_components/backend/caption/caption.js", "/html_editor/static/src/others/embedded_components/backend/file/file.js", "/html_editor/static/src/others/embedded_components/backend/syntax_highlighting/code_toolbar.js", "/html_editor/static/src/others/embedded_components/backend/syntax_highlighting/syntax_highlighting.js", "/html_editor/static/src/others/embedded_components/backend/video/video.js", "/html_editor/static/src/others/embedded_components/embedding_sets.js", "/html_editor/static/src/others/embedded_components/plugins/caption_plugin/caption_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/embedded_file_plugin/embedded_file_documents_selector.js", "/html_editor/static/src/others/embedded_components/plugins/embedded_file_plugin/embedded_file_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/syntax_highlighting_plugin/syntax_highlighting_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/table_of_content_plugin/table_of_content_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/toggle_block_plugin/toggle_block_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/video_plugin/embedded_video_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/video_plugin/embedded_youtube_plugin.js", "/html_editor/static/src/others/embedded_components/plugins/video_plugin/video_selector_dialog/embedded_video_selector.js", "/html_editor/static/src/others/embedded_component_plugin.js", "/html_editor/static/src/others/qweb_picker.js", "/html_editor/static/src/others/qweb_plugin.js", "/html_editor/static/src/services/upload_local_files_service.js", "/website_sale_comparison/static/src/interactions/comparison_page.js", "/website_sale_comparison/static/src/interactions/product_comparison.js", "/website_sale_comparison/static/src/js/product_comparison_bottom_bar/product_comparison_bottom_bar.js", "/website_sale_comparison/static/src/js/product_row/product_row.js", "/website_sale_comparison/static/src/js/website_sale_comparison_utils.js", "/website_sale_wishlist/static/src/interactions/add_product_to_wishlist_button.js", "/website_sale_wishlist/static/src/interactions/product_detail.js", "/website_sale_wishlist/static/src/interactions/product_wishlist.js", "/website_sale_wishlist/static/src/interactions/wishlist_navbar.js", "/website_sale_wishlist/static/src/js/website_sale_wishlist_utils.js", "/website_sale_comparison_wishlist/static/src/interactions/product_comparison.js", "/website_sale_mondialrelay/static/src/interactions/checkout.js", "/website_sale_stock/static/src/interactions/website_sale.js", "/website_sale_stock/static/src/js/combo_configurator_dialog/combo_configurator_dialog.js", "/website_sale_stock/static/src/js/models/product_product.js", "/website_sale_stock/static/src/js/product/product.js", "/website_sale_stock/static/src/js/product_card/product_card.js", "/website_sale_stock/static/src/js/product_configurator_dialog/product_configurator_dialog.js", "/website_sale_stock_wishlist/static/src/interactions/add_product_to_wishlist_button.js", "/website_sale_stock_wishlist/static/src/interactions/website_sale.js", "/website_blog/static/src/interactions/post_link.js", "/website_blog/static/src/interactions/website_blog.js", "/website_blog/static/src/snippets/s_blog_posts/blog_posts.js", "/website_forum/static/src/interactions/website_forum.js", "/website_forum/static/src/interactions/website_forum_activity.js", "/website_forum/static/src/interactions/website_forum_share.js", "/website_forum/static/src/interactions/website_forum_spam.js", "/website_forum/static/src/js/tours/website_forum.js", "/website_forum/static/src/components/flag_mark_as_offensive/flag_mark_as_offensive.js", "/website_forum/static/src/components/website_forum_tags_wrapper.js", "/website_forum/static/src/components/website_forum_wysiwyg/resizer_hook.js", "/website_forum/static/src/components/website_forum_wysiwyg/website_forum_wysiwyg.js", "/website_forum/static/src/plugins/font_plugin.js", "/website_forum/static/src/plugins/history_plugin.js", "/website_forum/static/src/plugins/plugin_sets.js", "/payment_demo/static/src/interactions/express_checkout.js", "/payment_demo/static/src/interactions/payment_demo_mixin.js", "/payment_demo/static/src/interactions/payment_form.js", "/payment_paypal/static/src/interactions/payment_button.js", "/payment_paypal/static/src/interactions/payment_form.js", "/payment_sepa_direct_debit/static/src/interactions/payment_form.js", "/website/static/src/interactions/ripple_effect.js"], "mappings": "AAAA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC95QA;;;;;AAAA;AACA;AACA;AACA;AACA;ACJA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7rMA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;ACLA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChvVA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjyDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzRA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7OA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1CA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3IA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjHA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClHA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjHA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvIA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvJA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1FA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/EA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpYA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzPA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnZA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChUA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtPA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACliBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnRA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7RA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACTA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtQA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/eA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7TA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvaA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtWA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9qBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtoBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACviBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1aA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpYA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1OA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxQA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjeA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrsBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvSA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3JA;;;;;;;;AAAA;AACA;AACA;AACA;;;;ACHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9OA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvcA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9jBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5KA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9KA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/ZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtUA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACn4BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpeA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxYA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5TA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9RA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3QA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5VA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvaA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrWA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3RA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACneA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9jCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/RA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpSA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClSA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpYA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrcA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7YA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/4BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrlBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/NA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtSA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/dA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACveA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxUA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvaA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtUA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpmBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC13BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/kBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnWA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrUA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvSA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3NA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/oBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9OA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7bA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjQA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7VA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3kBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9vBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9iBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5dA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/KA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzoBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5RA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7iBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9ZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/SA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpgBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5YA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvMA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1OA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACTA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1hDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpWA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACn7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACn3DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5LA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3lCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7XA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/iBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACloBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvQA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzsBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1wCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxaA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxgBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9WA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3iBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/QA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC71CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtWA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9iBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACt1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpTA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9KA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5KA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3SA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACllBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/UA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtPA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACTA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1iBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "sourcesContent": ["var luxon = (function (exports) {\n  'use strict';\n\n  function _defineProperties(target, props) {\n    for (var i = 0; i < props.length; i++) {\n      var descriptor = props[i];\n      descriptor.enumerable = descriptor.enumerable || false;\n      descriptor.configurable = true;\n      if (\"value\" in descriptor) descriptor.writable = true;\n      Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);\n    }\n  }\n  function _createClass(Constructor, protoProps, staticProps) {\n    if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n    if (staticProps) _defineProperties(Constructor, staticProps);\n    Object.defineProperty(Constructor, \"prototype\", {\n      writable: false\n    });\n    return Constructor;\n  }\n  function _extends() {\n    _extends = Object.assign ? Object.assign.bind() : function (target) {\n      for (var i = 1; i < arguments.length; i++) {\n        var source = arguments[i];\n        for (var key in source) {\n          if (Object.prototype.hasOwnProperty.call(source, key)) {\n            target[key] = source[key];\n          }\n        }\n      }\n      return target;\n    };\n    return _extends.apply(this, arguments);\n  }\n  function _inheritsLoose(subClass, superClass) {\n    subClass.prototype = Object.create(superClass.prototype);\n    subClass.prototype.constructor = subClass;\n    _setPrototypeOf(subClass, superClass);\n  }\n  function _getPrototypeOf(o) {\n    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {\n      return o.__proto__ || Object.getPrototypeOf(o);\n    };\n    return _getPrototypeOf(o);\n  }\n  function _setPrototypeOf(o, p) {\n    _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {\n      o.__proto__ = p;\n      return o;\n    };\n    return _setPrototypeOf(o, p);\n  }\n  function _isNativeReflectConstruct() {\n    if (typeof Reflect === \"undefined\" || !Reflect.construct) return false;\n    if (Reflect.construct.sham) return false;\n    if (typeof Proxy === \"function\") return true;\n    try {\n      Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n  function _construct(Parent, args, Class) {\n    if (_isNativeReflectConstruct()) {\n      _construct = Reflect.construct.bind();\n    } else {\n      _construct = function _construct(Parent, args, Class) {\n        var a = [null];\n        a.push.apply(a, args);\n        var Constructor = Function.bind.apply(Parent, a);\n        var instance = new Constructor();\n        if (Class) _setPrototypeOf(instance, Class.prototype);\n        return instance;\n      };\n    }\n    return _construct.apply(null, arguments);\n  }\n  function _isNativeFunction(fn) {\n    return Function.toString.call(fn).indexOf(\"[native code]\") !== -1;\n  }\n  function _wrapNativeSuper(Class) {\n    var _cache = typeof Map === \"function\" ? new Map() : undefined;\n    _wrapNativeSuper = function _wrapNativeSuper(Class) {\n      if (Class === null || !_isNativeFunction(Class)) return Class;\n      if (typeof Class !== \"function\") {\n        throw new TypeError(\"Super expression must either be null or a function\");\n      }\n      if (typeof _cache !== \"undefined\") {\n        if (_cache.has(Class)) return _cache.get(Class);\n        _cache.set(Class, Wrapper);\n      }\n      function Wrapper() {\n        return _construct(Class, arguments, _getPrototypeOf(this).constructor);\n      }\n      Wrapper.prototype = Object.create(Class.prototype, {\n        constructor: {\n          value: Wrapper,\n          enumerable: false,\n          writable: true,\n          configurable: true\n        }\n      });\n      return _setPrototypeOf(Wrapper, Class);\n    };\n    return _wrapNativeSuper(Class);\n  }\n  function _objectWithoutPropertiesLoose(source, excluded) {\n    if (source == null) return {};\n    var target = {};\n    var sourceKeys = Object.keys(source);\n    var key, i;\n    for (i = 0; i < sourceKeys.length; i++) {\n      key = sourceKeys[i];\n      if (excluded.indexOf(key) >= 0) continue;\n      target[key] = source[key];\n    }\n    return target;\n  }\n  function _unsupportedIterableToArray(o, minLen) {\n    if (!o) return;\n    if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n    var n = Object.prototype.toString.call(o).slice(8, -1);\n    if (n === \"Object\" && o.constructor) n = o.constructor.name;\n    if (n === \"Map\" || n === \"Set\") return Array.from(o);\n    if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n  }\n  function _arrayLikeToArray(arr, len) {\n    if (len == null || len > arr.length) len = arr.length;\n    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n    return arr2;\n  }\n  function _createForOfIteratorHelperLoose(o, allowArrayLike) {\n    var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"];\n    if (it) return (it = it.call(o)).next.bind(it);\n    if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") {\n      if (it) o = it;\n      var i = 0;\n      return function () {\n        if (i >= o.length) return {\n          done: true\n        };\n        return {\n          done: false,\n          value: o[i++]\n        };\n      };\n    }\n    throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n  }\n  function _toPrimitive(input, hint) {\n    if (typeof input !== \"object\" || input === null) return input;\n    var prim = input[Symbol.toPrimitive];\n    if (prim !== undefined) {\n      var res = prim.call(input, hint || \"default\");\n      if (typeof res !== \"object\") return res;\n      throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n    }\n    return (hint === \"string\" ? String : Number)(input);\n  }\n  function _toPropertyKey(arg) {\n    var key = _toPrimitive(arg, \"string\");\n    return typeof key === \"symbol\" ? key : String(key);\n  }\n\n  // these aren't really private, but nor are they really useful to document\n  /**\n   * @private\n   */\n  var LuxonError = /*#__PURE__*/function (_Error) {\n    _inheritsLoose(LuxonError, _Error);\n    function LuxonError() {\n      return _Error.apply(this, arguments) || this;\n    }\n    return LuxonError;\n  }( /*#__PURE__*/_wrapNativeSuper(Error));\n  /**\n   * @private\n   */\n  var InvalidDateTimeError = /*#__PURE__*/function (_LuxonError) {\n    _inheritsLoose(InvalidDateTimeError, _LuxonError);\n    function InvalidDateTimeError(reason) {\n      return _LuxonError.call(this, \"Invalid DateTime: \" + reason.toMessage()) || this;\n    }\n    return InvalidDateTimeError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var InvalidIntervalError = /*#__PURE__*/function (_LuxonError2) {\n    _inheritsLoose(InvalidIntervalError, _LuxonError2);\n    function InvalidIntervalError(reason) {\n      return _LuxonError2.call(this, \"Invalid Interval: \" + reason.toMessage()) || this;\n    }\n    return InvalidIntervalError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var InvalidDurationError = /*#__PURE__*/function (_LuxonError3) {\n    _inheritsLoose(InvalidDurationError, _LuxonError3);\n    function InvalidDurationError(reason) {\n      return _LuxonError3.call(this, \"Invalid Duration: \" + reason.toMessage()) || this;\n    }\n    return InvalidDurationError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var ConflictingSpecificationError = /*#__PURE__*/function (_LuxonError4) {\n    _inheritsLoose(ConflictingSpecificationError, _LuxonError4);\n    function ConflictingSpecificationError() {\n      return _LuxonError4.apply(this, arguments) || this;\n    }\n    return ConflictingSpecificationError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var InvalidUnitError = /*#__PURE__*/function (_LuxonError5) {\n    _inheritsLoose(InvalidUnitError, _LuxonError5);\n    function InvalidUnitError(unit) {\n      return _LuxonError5.call(this, \"Invalid unit \" + unit) || this;\n    }\n    return InvalidUnitError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var InvalidArgumentError = /*#__PURE__*/function (_LuxonError6) {\n    _inheritsLoose(InvalidArgumentError, _LuxonError6);\n    function InvalidArgumentError() {\n      return _LuxonError6.apply(this, arguments) || this;\n    }\n    return InvalidArgumentError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var ZoneIsAbstractError = /*#__PURE__*/function (_LuxonError7) {\n    _inheritsLoose(ZoneIsAbstractError, _LuxonError7);\n    function ZoneIsAbstractError() {\n      return _LuxonError7.call(this, \"Zone is an abstract class\") || this;\n    }\n    return ZoneIsAbstractError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n\n  var n = \"numeric\",\n    s = \"short\",\n    l = \"long\";\n  var DATE_SHORT = {\n    year: n,\n    month: n,\n    day: n\n  };\n  var DATE_MED = {\n    year: n,\n    month: s,\n    day: n\n  };\n  var DATE_MED_WITH_WEEKDAY = {\n    year: n,\n    month: s,\n    day: n,\n    weekday: s\n  };\n  var DATE_FULL = {\n    year: n,\n    month: l,\n    day: n\n  };\n  var DATE_HUGE = {\n    year: n,\n    month: l,\n    day: n,\n    weekday: l\n  };\n  var TIME_SIMPLE = {\n    hour: n,\n    minute: n\n  };\n  var TIME_WITH_SECONDS = {\n    hour: n,\n    minute: n,\n    second: n\n  };\n  var TIME_WITH_SHORT_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: s\n  };\n  var TIME_WITH_LONG_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: l\n  };\n  var TIME_24_SIMPLE = {\n    hour: n,\n    minute: n,\n    hourCycle: \"h23\"\n  };\n  var TIME_24_WITH_SECONDS = {\n    hour: n,\n    minute: n,\n    second: n,\n    hourCycle: \"h23\"\n  };\n  var TIME_24_WITH_SHORT_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    hourCycle: \"h23\",\n    timeZoneName: s\n  };\n  var TIME_24_WITH_LONG_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    hourCycle: \"h23\",\n    timeZoneName: l\n  };\n  var DATETIME_SHORT = {\n    year: n,\n    month: n,\n    day: n,\n    hour: n,\n    minute: n\n  };\n  var DATETIME_SHORT_WITH_SECONDS = {\n    year: n,\n    month: n,\n    day: n,\n    hour: n,\n    minute: n,\n    second: n\n  };\n  var DATETIME_MED = {\n    year: n,\n    month: s,\n    day: n,\n    hour: n,\n    minute: n\n  };\n  var DATETIME_MED_WITH_SECONDS = {\n    year: n,\n    month: s,\n    day: n,\n    hour: n,\n    minute: n,\n    second: n\n  };\n  var DATETIME_MED_WITH_WEEKDAY = {\n    year: n,\n    month: s,\n    day: n,\n    weekday: s,\n    hour: n,\n    minute: n\n  };\n  var DATETIME_FULL = {\n    year: n,\n    month: l,\n    day: n,\n    hour: n,\n    minute: n,\n    timeZoneName: s\n  };\n  var DATETIME_FULL_WITH_SECONDS = {\n    year: n,\n    month: l,\n    day: n,\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: s\n  };\n  var DATETIME_HUGE = {\n    year: n,\n    month: l,\n    day: n,\n    weekday: l,\n    hour: n,\n    minute: n,\n    timeZoneName: l\n  };\n  var DATETIME_HUGE_WITH_SECONDS = {\n    year: n,\n    month: l,\n    day: n,\n    weekday: l,\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: l\n  };\n\n  /**\n   * @interface\n   */\n  var Zone = /*#__PURE__*/function () {\n    function Zone() {}\n    var _proto = Zone.prototype;\n    /**\n     * Returns the offset's common name (such as EST) at the specified timestamp\n     * @abstract\n     * @param {number} ts - Epoch milliseconds for which to get the name\n     * @param {Object} opts - Options to affect the format\n     * @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'.\n     * @param {string} opts.locale - What locale to return the offset name in.\n     * @return {string}\n     */\n    _proto.offsetName = function offsetName(ts, opts) {\n      throw new ZoneIsAbstractError();\n    }\n\n    /**\n     * Returns the offset's value as a string\n     * @abstract\n     * @param {number} ts - Epoch milliseconds for which to get the offset\n     * @param {string} format - What style of offset to return.\n     *                          Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively\n     * @return {string}\n     */;\n    _proto.formatOffset = function formatOffset(ts, format) {\n      throw new ZoneIsAbstractError();\n    }\n\n    /**\n     * Return the offset in minutes for this zone at the specified timestamp.\n     * @abstract\n     * @param {number} ts - Epoch milliseconds for which to compute the offset\n     * @return {number}\n     */;\n    _proto.offset = function offset(ts) {\n      throw new ZoneIsAbstractError();\n    }\n\n    /**\n     * Return whether this Zone is equal to another zone\n     * @abstract\n     * @param {Zone} otherZone - the zone to compare\n     * @return {boolean}\n     */;\n    _proto.equals = function equals(otherZone) {\n      throw new ZoneIsAbstractError();\n    }\n\n    /**\n     * Return whether this Zone is valid.\n     * @abstract\n     * @type {boolean}\n     */;\n    _createClass(Zone, [{\n      key: \"type\",\n      get:\n      /**\n       * The type of zone\n       * @abstract\n       * @type {string}\n       */\n      function get() {\n        throw new ZoneIsAbstractError();\n      }\n\n      /**\n       * The name of this zone.\n       * @abstract\n       * @type {string}\n       */\n    }, {\n      key: \"name\",\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n\n      /**\n       * The IANA name of this zone.\n       * Defaults to `name` if not overwritten by a subclass.\n       * @abstract\n       * @type {string}\n       */\n    }, {\n      key: \"ianaName\",\n      get: function get() {\n        return this.name;\n      }\n\n      /**\n       * Returns whether the offset is known to be fixed for the whole year.\n       * @abstract\n       * @type {boolean}\n       */\n    }, {\n      key: \"isUniversal\",\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n    }]);\n    return Zone;\n  }();\n\n  var singleton$1 = null;\n\n  /**\n   * Represents the local zone for this JavaScript environment.\n   * @implements {Zone}\n   */\n  var SystemZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(SystemZone, _Zone);\n    function SystemZone() {\n      return _Zone.apply(this, arguments) || this;\n    }\n    var _proto = SystemZone.prototype;\n    /** @override **/\n    _proto.offsetName = function offsetName(ts, _ref) {\n      var format = _ref.format,\n        locale = _ref.locale;\n      return parseZoneInfo(ts, format, locale);\n    }\n\n    /** @override **/;\n    _proto.formatOffset = function formatOffset$1(ts, format) {\n      return formatOffset(this.offset(ts), format);\n    }\n\n    /** @override **/;\n    _proto.offset = function offset(ts) {\n      return -new Date(ts).getTimezoneOffset();\n    }\n\n    /** @override **/;\n    _proto.equals = function equals(otherZone) {\n      return otherZone.type === \"system\";\n    }\n\n    /** @override **/;\n    _createClass(SystemZone, [{\n      key: \"type\",\n      get: /** @override **/\n      function get() {\n        return \"system\";\n      }\n\n      /** @override **/\n    }, {\n      key: \"name\",\n      get: function get() {\n        return new Intl.DateTimeFormat().resolvedOptions().timeZone;\n      }\n\n      /** @override **/\n    }, {\n      key: \"isUniversal\",\n      get: function get() {\n        return false;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return true;\n      }\n    }], [{\n      key: \"instance\",\n      get:\n      /**\n       * Get a singleton instance of the local zone\n       * @return {SystemZone}\n       */\n      function get() {\n        if (singleton$1 === null) {\n          singleton$1 = new SystemZone();\n        }\n        return singleton$1;\n      }\n    }]);\n    return SystemZone;\n  }(Zone);\n\n  var dtfCache = {};\n  function makeDTF(zone) {\n    if (!dtfCache[zone]) {\n      dtfCache[zone] = new Intl.DateTimeFormat(\"en-US\", {\n        hour12: false,\n        timeZone: zone,\n        year: \"numeric\",\n        month: \"2-digit\",\n        day: \"2-digit\",\n        hour: \"2-digit\",\n        minute: \"2-digit\",\n        second: \"2-digit\",\n        era: \"short\"\n      });\n    }\n    return dtfCache[zone];\n  }\n  var typeToPos = {\n    year: 0,\n    month: 1,\n    day: 2,\n    era: 3,\n    hour: 4,\n    minute: 5,\n    second: 6\n  };\n  function hackyOffset(dtf, date) {\n    var formatted = dtf.format(date).replace(/\\u200E/g, \"\"),\n      parsed = /(\\d+)\\/(\\d+)\\/(\\d+) (AD|BC),? (\\d+):(\\d+):(\\d+)/.exec(formatted),\n      fMonth = parsed[1],\n      fDay = parsed[2],\n      fYear = parsed[3],\n      fadOrBc = parsed[4],\n      fHour = parsed[5],\n      fMinute = parsed[6],\n      fSecond = parsed[7];\n    return [fYear, fMonth, fDay, fadOrBc, fHour, fMinute, fSecond];\n  }\n  function partsOffset(dtf, date) {\n    var formatted = dtf.formatToParts(date);\n    var filled = [];\n    for (var i = 0; i < formatted.length; i++) {\n      var _formatted$i = formatted[i],\n        type = _formatted$i.type,\n        value = _formatted$i.value;\n      var pos = typeToPos[type];\n      if (type === \"era\") {\n        filled[pos] = value;\n      } else if (!isUndefined(pos)) {\n        filled[pos] = parseInt(value, 10);\n      }\n    }\n    return filled;\n  }\n  var ianaZoneCache = {};\n  /**\n   * A zone identified by an IANA identifier, like America/New_York\n   * @implements {Zone}\n   */\n  var IANAZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(IANAZone, _Zone);\n    /**\n     * @param {string} name - Zone name\n     * @return {IANAZone}\n     */\n    IANAZone.create = function create(name) {\n      if (!ianaZoneCache[name]) {\n        ianaZoneCache[name] = new IANAZone(name);\n      }\n      return ianaZoneCache[name];\n    }\n\n    /**\n     * Reset local caches. Should only be necessary in testing scenarios.\n     * @return {void}\n     */;\n    IANAZone.resetCache = function resetCache() {\n      ianaZoneCache = {};\n      dtfCache = {};\n    }\n\n    /**\n     * Returns whether the provided string is a valid specifier. This only checks the string's format, not that the specifier identifies a known zone; see isValidZone for that.\n     * @param {string} s - The string to check validity on\n     * @example IANAZone.isValidSpecifier(\"America/New_York\") //=> true\n     * @example IANAZone.isValidSpecifier(\"Sport~~blorp\") //=> false\n     * @deprecated For backward compatibility, this forwards to isValidZone, better use `isValidZone()` directly instead.\n     * @return {boolean}\n     */;\n    IANAZone.isValidSpecifier = function isValidSpecifier(s) {\n      return this.isValidZone(s);\n    }\n\n    /**\n     * Returns whether the provided string identifies a real zone\n     * @param {string} zone - The string to check\n     * @example IANAZone.isValidZone(\"America/New_York\") //=> true\n     * @example IANAZone.isValidZone(\"Fantasia/Castle\") //=> false\n     * @example IANAZone.isValidZone(\"Sport~~blorp\") //=> false\n     * @return {boolean}\n     */;\n    IANAZone.isValidZone = function isValidZone(zone) {\n      if (!zone) {\n        return false;\n      }\n      try {\n        new Intl.DateTimeFormat(\"en-US\", {\n          timeZone: zone\n        }).format();\n        return true;\n      } catch (e) {\n        return false;\n      }\n    };\n    function IANAZone(name) {\n      var _this;\n      _this = _Zone.call(this) || this;\n      /** @private **/\n      _this.zoneName = name;\n      /** @private **/\n      _this.valid = IANAZone.isValidZone(name);\n      return _this;\n    }\n\n    /**\n     * The type of zone. `iana` for all instances of `IANAZone`.\n     * @override\n     * @type {string}\n     */\n    var _proto = IANAZone.prototype;\n    /**\n     * Returns the offset's common name (such as EST) at the specified timestamp\n     * @override\n     * @param {number} ts - Epoch milliseconds for which to get the name\n     * @param {Object} opts - Options to affect the format\n     * @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'.\n     * @param {string} opts.locale - What locale to return the offset name in.\n     * @return {string}\n     */\n    _proto.offsetName = function offsetName(ts, _ref) {\n      var format = _ref.format,\n        locale = _ref.locale;\n      return parseZoneInfo(ts, format, locale, this.name);\n    }\n\n    /**\n     * Returns the offset's value as a string\n     * @override\n     * @param {number} ts - Epoch milliseconds for which to get the offset\n     * @param {string} format - What style of offset to return.\n     *                          Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively\n     * @return {string}\n     */;\n    _proto.formatOffset = function formatOffset$1(ts, format) {\n      return formatOffset(this.offset(ts), format);\n    }\n\n    /**\n     * Return the offset in minutes for this zone at the specified timestamp.\n     * @override\n     * @param {number} ts - Epoch milliseconds for which to compute the offset\n     * @return {number}\n     */;\n    _proto.offset = function offset(ts) {\n      var date = new Date(ts);\n      if (isNaN(date)) return NaN;\n      var dtf = makeDTF(this.name);\n      var _ref2 = dtf.formatToParts ? partsOffset(dtf, date) : hackyOffset(dtf, date),\n        year = _ref2[0],\n        month = _ref2[1],\n        day = _ref2[2],\n        adOrBc = _ref2[3],\n        hour = _ref2[4],\n        minute = _ref2[5],\n        second = _ref2[6];\n      if (adOrBc === \"BC\") {\n        year = -Math.abs(year) + 1;\n      }\n\n      // because we're using hour12 and https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat\n      var adjustedHour = hour === 24 ? 0 : hour;\n      var asUTC = objToLocalTS({\n        year: year,\n        month: month,\n        day: day,\n        hour: adjustedHour,\n        minute: minute,\n        second: second,\n        millisecond: 0\n      });\n      var asTS = +date;\n      var over = asTS % 1000;\n      asTS -= over >= 0 ? over : 1000 + over;\n      return (asUTC - asTS) / (60 * 1000);\n    }\n\n    /**\n     * Return whether this Zone is equal to another zone\n     * @override\n     * @param {Zone} otherZone - the zone to compare\n     * @return {boolean}\n     */;\n    _proto.equals = function equals(otherZone) {\n      return otherZone.type === \"iana\" && otherZone.name === this.name;\n    }\n\n    /**\n     * Return whether this Zone is valid.\n     * @override\n     * @type {boolean}\n     */;\n    _createClass(IANAZone, [{\n      key: \"type\",\n      get: function get() {\n        return \"iana\";\n      }\n\n      /**\n       * The name of this zone (i.e. the IANA zone name).\n       * @override\n       * @type {string}\n       */\n    }, {\n      key: \"name\",\n      get: function get() {\n        return this.zoneName;\n      }\n\n      /**\n       * Returns whether the offset is known to be fixed for the whole year:\n       * Always returns false for all IANA zones.\n       * @override\n       * @type {boolean}\n       */\n    }, {\n      key: \"isUniversal\",\n      get: function get() {\n        return false;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return this.valid;\n      }\n    }]);\n    return IANAZone;\n  }(Zone);\n\n  var _excluded = [\"base\"],\n    _excluded2 = [\"padTo\", \"floor\"];\n\n  // todo - remap caching\n\n  var intlLFCache = {};\n  function getCachedLF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n    var key = JSON.stringify([locString, opts]);\n    var dtf = intlLFCache[key];\n    if (!dtf) {\n      dtf = new Intl.ListFormat(locString, opts);\n      intlLFCache[key] = dtf;\n    }\n    return dtf;\n  }\n  var intlDTCache = {};\n  function getCachedDTF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n    var key = JSON.stringify([locString, opts]);\n    var dtf = intlDTCache[key];\n    if (!dtf) {\n      dtf = new Intl.DateTimeFormat(locString, opts);\n      intlDTCache[key] = dtf;\n    }\n    return dtf;\n  }\n  var intlNumCache = {};\n  function getCachedINF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n    var key = JSON.stringify([locString, opts]);\n    var inf = intlNumCache[key];\n    if (!inf) {\n      inf = new Intl.NumberFormat(locString, opts);\n      intlNumCache[key] = inf;\n    }\n    return inf;\n  }\n  var intlRelCache = {};\n  function getCachedRTF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n    var _opts = opts;\n      _opts.base;\n      var cacheKeyOpts = _objectWithoutPropertiesLoose(_opts, _excluded); // exclude `base` from the options\n    var key = JSON.stringify([locString, cacheKeyOpts]);\n    var inf = intlRelCache[key];\n    if (!inf) {\n      inf = new Intl.RelativeTimeFormat(locString, opts);\n      intlRelCache[key] = inf;\n    }\n    return inf;\n  }\n  var sysLocaleCache = null;\n  function systemLocale() {\n    if (sysLocaleCache) {\n      return sysLocaleCache;\n    } else {\n      sysLocaleCache = new Intl.DateTimeFormat().resolvedOptions().locale;\n      return sysLocaleCache;\n    }\n  }\n  var weekInfoCache = {};\n  function getCachedWeekInfo(locString) {\n    var data = weekInfoCache[locString];\n    if (!data) {\n      var locale = new Intl.Locale(locString);\n      // browsers currently implement this as a property, but spec says it should be a getter function\n      data = \"getWeekInfo\" in locale ? locale.getWeekInfo() : locale.weekInfo;\n      weekInfoCache[locString] = data;\n    }\n    return data;\n  }\n  function parseLocaleString(localeStr) {\n    // I really want to avoid writing a BCP 47 parser\n    // see, e.g. https://github.com/wooorm/bcp-47\n    // Instead, we'll do this:\n\n    // a) if the string has no -u extensions, just leave it alone\n    // b) if it does, use Intl to resolve everything\n    // c) if Intl fails, try again without the -u\n\n    // private subtags and unicode subtags have ordering requirements,\n    // and we're not properly parsing this, so just strip out the\n    // private ones if they exist.\n    var xIndex = localeStr.indexOf(\"-x-\");\n    if (xIndex !== -1) {\n      localeStr = localeStr.substring(0, xIndex);\n    }\n    var uIndex = localeStr.indexOf(\"-u-\");\n    if (uIndex === -1) {\n      return [localeStr];\n    } else {\n      var options;\n      var selectedStr;\n      try {\n        options = getCachedDTF(localeStr).resolvedOptions();\n        selectedStr = localeStr;\n      } catch (e) {\n        var smaller = localeStr.substring(0, uIndex);\n        options = getCachedDTF(smaller).resolvedOptions();\n        selectedStr = smaller;\n      }\n      var _options = options,\n        numberingSystem = _options.numberingSystem,\n        calendar = _options.calendar;\n      return [selectedStr, numberingSystem, calendar];\n    }\n  }\n  function intlConfigString(localeStr, numberingSystem, outputCalendar) {\n    if (outputCalendar || numberingSystem) {\n      if (!localeStr.includes(\"-u-\")) {\n        localeStr += \"-u\";\n      }\n      if (outputCalendar) {\n        localeStr += \"-ca-\" + outputCalendar;\n      }\n      if (numberingSystem) {\n        localeStr += \"-nu-\" + numberingSystem;\n      }\n      return localeStr;\n    } else {\n      return localeStr;\n    }\n  }\n  function mapMonths(f) {\n    var ms = [];\n    for (var i = 1; i <= 12; i++) {\n      var dt = DateTime.utc(2009, i, 1);\n      ms.push(f(dt));\n    }\n    return ms;\n  }\n  function mapWeekdays(f) {\n    var ms = [];\n    for (var i = 1; i <= 7; i++) {\n      var dt = DateTime.utc(2016, 11, 13 + i);\n      ms.push(f(dt));\n    }\n    return ms;\n  }\n  function listStuff(loc, length, englishFn, intlFn) {\n    var mode = loc.listingMode();\n    if (mode === \"error\") {\n      return null;\n    } else if (mode === \"en\") {\n      return englishFn(length);\n    } else {\n      return intlFn(length);\n    }\n  }\n  function supportsFastNumbers(loc) {\n    if (loc.numberingSystem && loc.numberingSystem !== \"latn\") {\n      return false;\n    } else {\n      return loc.numberingSystem === \"latn\" || !loc.locale || loc.locale.startsWith(\"en\") || new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === \"latn\";\n    }\n  }\n\n  /**\n   * @private\n   */\n  var PolyNumberFormatter = /*#__PURE__*/function () {\n    function PolyNumberFormatter(intl, forceSimple, opts) {\n      this.padTo = opts.padTo || 0;\n      this.floor = opts.floor || false;\n      opts.padTo;\n        opts.floor;\n        var otherOpts = _objectWithoutPropertiesLoose(opts, _excluded2);\n      if (!forceSimple || Object.keys(otherOpts).length > 0) {\n        var intlOpts = _extends({\n          useGrouping: false\n        }, opts);\n        if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo;\n        this.inf = getCachedINF(intl, intlOpts);\n      }\n    }\n    var _proto = PolyNumberFormatter.prototype;\n    _proto.format = function format(i) {\n      if (this.inf) {\n        var fixed = this.floor ? Math.floor(i) : i;\n        return this.inf.format(fixed);\n      } else {\n        // to match the browser's numberformatter defaults\n        var _fixed = this.floor ? Math.floor(i) : roundTo(i, 3);\n        return padStart(_fixed, this.padTo);\n      }\n    };\n    return PolyNumberFormatter;\n  }();\n  /**\n   * @private\n   */\n  var PolyDateFormatter = /*#__PURE__*/function () {\n    function PolyDateFormatter(dt, intl, opts) {\n      this.opts = opts;\n      this.originalZone = undefined;\n      var z = undefined;\n      if (this.opts.timeZone) {\n        // Don't apply any workarounds if a timeZone is explicitly provided in opts\n        this.dt = dt;\n      } else if (dt.zone.type === \"fixed\") {\n        // UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like.\n        // That is why fixed-offset TZ is set to that unless it is:\n        // 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT.\n        // 2. Unsupported by the browser:\n        //    - some do not support Etc/\n        //    - < Etc/GMT-14, > Etc/GMT+12, and 30-minute or 45-minute offsets are not part of tzdata\n        var gmtOffset = -1 * (dt.offset / 60);\n        var offsetZ = gmtOffset >= 0 ? \"Etc/GMT+\" + gmtOffset : \"Etc/GMT\" + gmtOffset;\n        if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) {\n          z = offsetZ;\n          this.dt = dt;\n        } else {\n          // Not all fixed-offset zones like Etc/+4:30 are present in tzdata so\n          // we manually apply the offset and substitute the zone as needed.\n          z = \"UTC\";\n          this.dt = dt.offset === 0 ? dt : dt.setZone(\"UTC\").plus({\n            minutes: dt.offset\n          });\n          this.originalZone = dt.zone;\n        }\n      } else if (dt.zone.type === \"system\") {\n        this.dt = dt;\n      } else if (dt.zone.type === \"iana\") {\n        this.dt = dt;\n        z = dt.zone.name;\n      } else {\n        // Custom zones can have any offset / offsetName so we just manually\n        // apply the offset and substitute the zone as needed.\n        z = \"UTC\";\n        this.dt = dt.setZone(\"UTC\").plus({\n          minutes: dt.offset\n        });\n        this.originalZone = dt.zone;\n      }\n      var intlOpts = _extends({}, this.opts);\n      intlOpts.timeZone = intlOpts.timeZone || z;\n      this.dtf = getCachedDTF(intl, intlOpts);\n    }\n    var _proto2 = PolyDateFormatter.prototype;\n    _proto2.format = function format() {\n      if (this.originalZone) {\n        // If we have to substitute in the actual zone name, we have to use\n        // formatToParts so that the timezone can be replaced.\n        return this.formatToParts().map(function (_ref) {\n          var value = _ref.value;\n          return value;\n        }).join(\"\");\n      }\n      return this.dtf.format(this.dt.toJSDate());\n    };\n    _proto2.formatToParts = function formatToParts() {\n      var _this = this;\n      var parts = this.dtf.formatToParts(this.dt.toJSDate());\n      if (this.originalZone) {\n        return parts.map(function (part) {\n          if (part.type === \"timeZoneName\") {\n            var offsetName = _this.originalZone.offsetName(_this.dt.ts, {\n              locale: _this.dt.locale,\n              format: _this.opts.timeZoneName\n            });\n            return _extends({}, part, {\n              value: offsetName\n            });\n          } else {\n            return part;\n          }\n        });\n      }\n      return parts;\n    };\n    _proto2.resolvedOptions = function resolvedOptions() {\n      return this.dtf.resolvedOptions();\n    };\n    return PolyDateFormatter;\n  }();\n  /**\n   * @private\n   */\n  var PolyRelFormatter = /*#__PURE__*/function () {\n    function PolyRelFormatter(intl, isEnglish, opts) {\n      this.opts = _extends({\n        style: \"long\"\n      }, opts);\n      if (!isEnglish && hasRelative()) {\n        this.rtf = getCachedRTF(intl, opts);\n      }\n    }\n    var _proto3 = PolyRelFormatter.prototype;\n    _proto3.format = function format(count, unit) {\n      if (this.rtf) {\n        return this.rtf.format(count, unit);\n      } else {\n        return formatRelativeTime(unit, count, this.opts.numeric, this.opts.style !== \"long\");\n      }\n    };\n    _proto3.formatToParts = function formatToParts(count, unit) {\n      if (this.rtf) {\n        return this.rtf.formatToParts(count, unit);\n      } else {\n        return [];\n      }\n    };\n    return PolyRelFormatter;\n  }();\n  var fallbackWeekSettings = {\n    firstDay: 1,\n    minimalDays: 4,\n    weekend: [6, 7]\n  };\n\n  /**\n   * @private\n   */\n  var Locale = /*#__PURE__*/function () {\n    Locale.fromOpts = function fromOpts(opts) {\n      return Locale.create(opts.locale, opts.numberingSystem, opts.outputCalendar, opts.weekSettings, opts.defaultToEN);\n    };\n    Locale.create = function create(locale, numberingSystem, outputCalendar, weekSettings, defaultToEN) {\n      if (defaultToEN === void 0) {\n        defaultToEN = false;\n      }\n      var specifiedLocale = locale || Settings.defaultLocale;\n      // the system locale is useful for human-readable strings but annoying for parsing/formatting known formats\n      var localeR = specifiedLocale || (defaultToEN ? \"en-US\" : systemLocale());\n      var numberingSystemR = numberingSystem || Settings.defaultNumberingSystem;\n      var outputCalendarR = outputCalendar || Settings.defaultOutputCalendar;\n      var weekSettingsR = validateWeekSettings(weekSettings) || Settings.defaultWeekSettings;\n      return new Locale(localeR, numberingSystemR, outputCalendarR, weekSettingsR, specifiedLocale);\n    };\n    Locale.resetCache = function resetCache() {\n      sysLocaleCache = null;\n      intlDTCache = {};\n      intlNumCache = {};\n      intlRelCache = {};\n    };\n    Locale.fromObject = function fromObject(_temp) {\n      var _ref2 = _temp === void 0 ? {} : _temp,\n        locale = _ref2.locale,\n        numberingSystem = _ref2.numberingSystem,\n        outputCalendar = _ref2.outputCalendar,\n        weekSettings = _ref2.weekSettings;\n      return Locale.create(locale, numberingSystem, outputCalendar, weekSettings);\n    };\n    function Locale(locale, numbering, outputCalendar, weekSettings, specifiedLocale) {\n      var _parseLocaleString = parseLocaleString(locale),\n        parsedLocale = _parseLocaleString[0],\n        parsedNumberingSystem = _parseLocaleString[1],\n        parsedOutputCalendar = _parseLocaleString[2];\n      this.locale = parsedLocale;\n      this.numberingSystem = numbering || parsedNumberingSystem || null;\n      this.outputCalendar = outputCalendar || parsedOutputCalendar || null;\n      this.weekSettings = weekSettings;\n      this.intl = intlConfigString(this.locale, this.numberingSystem, this.outputCalendar);\n      this.weekdaysCache = {\n        format: {},\n        standalone: {}\n      };\n      this.monthsCache = {\n        format: {},\n        standalone: {}\n      };\n      this.meridiemCache = null;\n      this.eraCache = {};\n      this.specifiedLocale = specifiedLocale;\n      this.fastNumbersCached = null;\n    }\n    var _proto4 = Locale.prototype;\n    _proto4.listingMode = function listingMode() {\n      var isActuallyEn = this.isEnglish();\n      var hasNoWeirdness = (this.numberingSystem === null || this.numberingSystem === \"latn\") && (this.outputCalendar === null || this.outputCalendar === \"gregory\");\n      return isActuallyEn && hasNoWeirdness ? \"en\" : \"intl\";\n    };\n    _proto4.clone = function clone(alts) {\n      if (!alts || Object.getOwnPropertyNames(alts).length === 0) {\n        return this;\n      } else {\n        return Locale.create(alts.locale || this.specifiedLocale, alts.numberingSystem || this.numberingSystem, alts.outputCalendar || this.outputCalendar, validateWeekSettings(alts.weekSettings) || this.weekSettings, alts.defaultToEN || false);\n      }\n    };\n    _proto4.redefaultToEN = function redefaultToEN(alts) {\n      if (alts === void 0) {\n        alts = {};\n      }\n      return this.clone(_extends({}, alts, {\n        defaultToEN: true\n      }));\n    };\n    _proto4.redefaultToSystem = function redefaultToSystem(alts) {\n      if (alts === void 0) {\n        alts = {};\n      }\n      return this.clone(_extends({}, alts, {\n        defaultToEN: false\n      }));\n    };\n    _proto4.months = function months$1(length, format) {\n      var _this2 = this;\n      if (format === void 0) {\n        format = false;\n      }\n      return listStuff(this, length, months, function () {\n        var intl = format ? {\n            month: length,\n            day: \"numeric\"\n          } : {\n            month: length\n          },\n          formatStr = format ? \"format\" : \"standalone\";\n        if (!_this2.monthsCache[formatStr][length]) {\n          _this2.monthsCache[formatStr][length] = mapMonths(function (dt) {\n            return _this2.extract(dt, intl, \"month\");\n          });\n        }\n        return _this2.monthsCache[formatStr][length];\n      });\n    };\n    _proto4.weekdays = function weekdays$1(length, format) {\n      var _this3 = this;\n      if (format === void 0) {\n        format = false;\n      }\n      return listStuff(this, length, weekdays, function () {\n        var intl = format ? {\n            weekday: length,\n            year: \"numeric\",\n            month: \"long\",\n            day: \"numeric\"\n          } : {\n            weekday: length\n          },\n          formatStr = format ? \"format\" : \"standalone\";\n        if (!_this3.weekdaysCache[formatStr][length]) {\n          _this3.weekdaysCache[formatStr][length] = mapWeekdays(function (dt) {\n            return _this3.extract(dt, intl, \"weekday\");\n          });\n        }\n        return _this3.weekdaysCache[formatStr][length];\n      });\n    };\n    _proto4.meridiems = function meridiems$1() {\n      var _this4 = this;\n      return listStuff(this, undefined, function () {\n        return meridiems;\n      }, function () {\n        // In theory there could be aribitrary day periods. We're gonna assume there are exactly two\n        // for AM and PM. This is probably wrong, but it's makes parsing way easier.\n        if (!_this4.meridiemCache) {\n          var intl = {\n            hour: \"numeric\",\n            hourCycle: \"h12\"\n          };\n          _this4.meridiemCache = [DateTime.utc(2016, 11, 13, 9), DateTime.utc(2016, 11, 13, 19)].map(function (dt) {\n            return _this4.extract(dt, intl, \"dayperiod\");\n          });\n        }\n        return _this4.meridiemCache;\n      });\n    };\n    _proto4.eras = function eras$1(length) {\n      var _this5 = this;\n      return listStuff(this, length, eras, function () {\n        var intl = {\n          era: length\n        };\n\n        // This is problematic. Different calendars are going to define eras totally differently. What I need is the minimum set of dates\n        // to definitely enumerate them.\n        if (!_this5.eraCache[length]) {\n          _this5.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map(function (dt) {\n            return _this5.extract(dt, intl, \"era\");\n          });\n        }\n        return _this5.eraCache[length];\n      });\n    };\n    _proto4.extract = function extract(dt, intlOpts, field) {\n      var df = this.dtFormatter(dt, intlOpts),\n        results = df.formatToParts(),\n        matching = results.find(function (m) {\n          return m.type.toLowerCase() === field;\n        });\n      return matching ? matching.value : null;\n    };\n    _proto4.numberFormatter = function numberFormatter(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      // this forcesimple option is never used (the only caller short-circuits on it, but it seems safer to leave)\n      // (in contrast, the rest of the condition is used heavily)\n      return new PolyNumberFormatter(this.intl, opts.forceSimple || this.fastNumbers, opts);\n    };\n    _proto4.dtFormatter = function dtFormatter(dt, intlOpts) {\n      if (intlOpts === void 0) {\n        intlOpts = {};\n      }\n      return new PolyDateFormatter(dt, this.intl, intlOpts);\n    };\n    _proto4.relFormatter = function relFormatter(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return new PolyRelFormatter(this.intl, this.isEnglish(), opts);\n    };\n    _proto4.listFormatter = function listFormatter(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return getCachedLF(this.intl, opts);\n    };\n    _proto4.isEnglish = function isEnglish() {\n      return this.locale === \"en\" || this.locale.toLowerCase() === \"en-us\" || new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith(\"en-us\");\n    };\n    _proto4.getWeekSettings = function getWeekSettings() {\n      if (this.weekSettings) {\n        return this.weekSettings;\n      } else if (!hasLocaleWeekInfo()) {\n        return fallbackWeekSettings;\n      } else {\n        return getCachedWeekInfo(this.locale);\n      }\n    };\n    _proto4.getStartOfWeek = function getStartOfWeek() {\n      return this.getWeekSettings().firstDay;\n    };\n    _proto4.getMinDaysInFirstWeek = function getMinDaysInFirstWeek() {\n      return this.getWeekSettings().minimalDays;\n    };\n    _proto4.getWeekendDays = function getWeekendDays() {\n      return this.getWeekSettings().weekend;\n    };\n    _proto4.equals = function equals(other) {\n      return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;\n    };\n    _proto4.toString = function toString() {\n      return \"Locale(\" + this.locale + \", \" + this.numberingSystem + \", \" + this.outputCalendar + \")\";\n    };\n    _createClass(Locale, [{\n      key: \"fastNumbers\",\n      get: function get() {\n        if (this.fastNumbersCached == null) {\n          this.fastNumbersCached = supportsFastNumbers(this);\n        }\n        return this.fastNumbersCached;\n      }\n    }]);\n    return Locale;\n  }();\n\n  var singleton = null;\n\n  /**\n   * A zone with a fixed offset (meaning no DST)\n   * @implements {Zone}\n   */\n  var FixedOffsetZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(FixedOffsetZone, _Zone);\n    /**\n     * Get an instance with a specified offset\n     * @param {number} offset - The offset in minutes\n     * @return {FixedOffsetZone}\n     */\n    FixedOffsetZone.instance = function instance(offset) {\n      return offset === 0 ? FixedOffsetZone.utcInstance : new FixedOffsetZone(offset);\n    }\n\n    /**\n     * Get an instance of FixedOffsetZone from a UTC offset string, like \"UTC+6\"\n     * @param {string} s - The offset string to parse\n     * @example FixedOffsetZone.parseSpecifier(\"UTC+6\")\n     * @example FixedOffsetZone.parseSpecifier(\"UTC+06\")\n     * @example FixedOffsetZone.parseSpecifier(\"UTC-6:00\")\n     * @return {FixedOffsetZone}\n     */;\n    FixedOffsetZone.parseSpecifier = function parseSpecifier(s) {\n      if (s) {\n        var r = s.match(/^utc(?:([+-]\\d{1,2})(?::(\\d{2}))?)?$/i);\n        if (r) {\n          return new FixedOffsetZone(signedOffset(r[1], r[2]));\n        }\n      }\n      return null;\n    };\n    function FixedOffsetZone(offset) {\n      var _this;\n      _this = _Zone.call(this) || this;\n      /** @private **/\n      _this.fixed = offset;\n      return _this;\n    }\n\n    /**\n     * The type of zone. `fixed` for all instances of `FixedOffsetZone`.\n     * @override\n     * @type {string}\n     */\n    var _proto = FixedOffsetZone.prototype;\n    /**\n     * Returns the offset's common name at the specified timestamp.\n     *\n     * For fixed offset zones this equals to the zone name.\n     * @override\n     */\n    _proto.offsetName = function offsetName() {\n      return this.name;\n    }\n\n    /**\n     * Returns the offset's value as a string\n     * @override\n     * @param {number} ts - Epoch milliseconds for which to get the offset\n     * @param {string} format - What style of offset to return.\n     *                          Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively\n     * @return {string}\n     */;\n    _proto.formatOffset = function formatOffset$1(ts, format) {\n      return formatOffset(this.fixed, format);\n    }\n\n    /**\n     * Returns whether the offset is known to be fixed for the whole year:\n     * Always returns true for all fixed offset zones.\n     * @override\n     * @type {boolean}\n     */;\n    /**\n     * Return the offset in minutes for this zone at the specified timestamp.\n     *\n     * For fixed offset zones, this is constant and does not depend on a timestamp.\n     * @override\n     * @return {number}\n     */\n    _proto.offset = function offset() {\n      return this.fixed;\n    }\n\n    /**\n     * Return whether this Zone is equal to another zone (i.e. also fixed and same offset)\n     * @override\n     * @param {Zone} otherZone - the zone to compare\n     * @return {boolean}\n     */;\n    _proto.equals = function equals(otherZone) {\n      return otherZone.type === \"fixed\" && otherZone.fixed === this.fixed;\n    }\n\n    /**\n     * Return whether this Zone is valid:\n     * All fixed offset zones are valid.\n     * @override\n     * @type {boolean}\n     */;\n    _createClass(FixedOffsetZone, [{\n      key: \"type\",\n      get: function get() {\n        return \"fixed\";\n      }\n\n      /**\n       * The name of this zone.\n       * All fixed zones' names always start with \"UTC\" (plus optional offset)\n       * @override\n       * @type {string}\n       */\n    }, {\n      key: \"name\",\n      get: function get() {\n        return this.fixed === 0 ? \"UTC\" : \"UTC\" + formatOffset(this.fixed, \"narrow\");\n      }\n\n      /**\n       * The IANA name of this zone, i.e. `Etc/UTC` or `Etc/GMT+/-nn`\n       *\n       * @override\n       * @type {string}\n       */\n    }, {\n      key: \"ianaName\",\n      get: function get() {\n        if (this.fixed === 0) {\n          return \"Etc/UTC\";\n        } else {\n          return \"Etc/GMT\" + formatOffset(-this.fixed, \"narrow\");\n        }\n      }\n    }, {\n      key: \"isUniversal\",\n      get: function get() {\n        return true;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return true;\n      }\n    }], [{\n      key: \"utcInstance\",\n      get:\n      /**\n       * Get a singleton instance of UTC\n       * @return {FixedOffsetZone}\n       */\n      function get() {\n        if (singleton === null) {\n          singleton = new FixedOffsetZone(0);\n        }\n        return singleton;\n      }\n    }]);\n    return FixedOffsetZone;\n  }(Zone);\n\n  /**\n   * A zone that failed to parse. You should never need to instantiate this.\n   * @implements {Zone}\n   */\n  var InvalidZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(InvalidZone, _Zone);\n    function InvalidZone(zoneName) {\n      var _this;\n      _this = _Zone.call(this) || this;\n      /**  @private */\n      _this.zoneName = zoneName;\n      return _this;\n    }\n\n    /** @override **/\n    var _proto = InvalidZone.prototype;\n    /** @override **/\n    _proto.offsetName = function offsetName() {\n      return null;\n    }\n\n    /** @override **/;\n    _proto.formatOffset = function formatOffset() {\n      return \"\";\n    }\n\n    /** @override **/;\n    _proto.offset = function offset() {\n      return NaN;\n    }\n\n    /** @override **/;\n    _proto.equals = function equals() {\n      return false;\n    }\n\n    /** @override **/;\n    _createClass(InvalidZone, [{\n      key: \"type\",\n      get: function get() {\n        return \"invalid\";\n      }\n\n      /** @override **/\n    }, {\n      key: \"name\",\n      get: function get() {\n        return this.zoneName;\n      }\n\n      /** @override **/\n    }, {\n      key: \"isUniversal\",\n      get: function get() {\n        return false;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return false;\n      }\n    }]);\n    return InvalidZone;\n  }(Zone);\n\n  /**\n   * @private\n   */\n  function normalizeZone(input, defaultZone) {\n    if (isUndefined(input) || input === null) {\n      return defaultZone;\n    } else if (input instanceof Zone) {\n      return input;\n    } else if (isString(input)) {\n      var lowered = input.toLowerCase();\n      if (lowered === \"default\") return defaultZone;else if (lowered === \"local\" || lowered === \"system\") return SystemZone.instance;else if (lowered === \"utc\" || lowered === \"gmt\") return FixedOffsetZone.utcInstance;else return FixedOffsetZone.parseSpecifier(lowered) || IANAZone.create(input);\n    } else if (isNumber(input)) {\n      return FixedOffsetZone.instance(input);\n    } else if (typeof input === \"object\" && \"offset\" in input && typeof input.offset === \"function\") {\n      // This is dumb, but the instanceof check above doesn't seem to really work\n      // so we're duck checking it\n      return input;\n    } else {\n      return new InvalidZone(input);\n    }\n  }\n\n  var numberingSystems = {\n    arab: \"[\\u0660-\\u0669]\",\n    arabext: \"[\\u06F0-\\u06F9]\",\n    bali: \"[\\u1B50-\\u1B59]\",\n    beng: \"[\\u09E6-\\u09EF]\",\n    deva: \"[\\u0966-\\u096F]\",\n    fullwide: \"[\\uFF10-\\uFF19]\",\n    gujr: \"[\\u0AE6-\\u0AEF]\",\n    hanidec: \"[\u3007|\u4e00|\u4e8c|\u4e09|\u56db|\u4e94|\u516d|\u4e03|\u516b|\u4e5d]\",\n    khmr: \"[\\u17E0-\\u17E9]\",\n    knda: \"[\\u0CE6-\\u0CEF]\",\n    laoo: \"[\\u0ED0-\\u0ED9]\",\n    limb: \"[\\u1946-\\u194F]\",\n    mlym: \"[\\u0D66-\\u0D6F]\",\n    mong: \"[\\u1810-\\u1819]\",\n    mymr: \"[\\u1040-\\u1049]\",\n    orya: \"[\\u0B66-\\u0B6F]\",\n    tamldec: \"[\\u0BE6-\\u0BEF]\",\n    telu: \"[\\u0C66-\\u0C6F]\",\n    thai: \"[\\u0E50-\\u0E59]\",\n    tibt: \"[\\u0F20-\\u0F29]\",\n    latn: \"\\\\d\"\n  };\n  var numberingSystemsUTF16 = {\n    arab: [1632, 1641],\n    arabext: [1776, 1785],\n    bali: [6992, 7001],\n    beng: [2534, 2543],\n    deva: [2406, 2415],\n    fullwide: [65296, 65303],\n    gujr: [2790, 2799],\n    khmr: [6112, 6121],\n    knda: [3302, 3311],\n    laoo: [3792, 3801],\n    limb: [6470, 6479],\n    mlym: [3430, 3439],\n    mong: [6160, 6169],\n    mymr: [4160, 4169],\n    orya: [2918, 2927],\n    tamldec: [3046, 3055],\n    telu: [3174, 3183],\n    thai: [3664, 3673],\n    tibt: [3872, 3881]\n  };\n  var hanidecChars = numberingSystems.hanidec.replace(/[\\[|\\]]/g, \"\").split(\"\");\n  function parseDigits(str) {\n    var value = parseInt(str, 10);\n    if (isNaN(value)) {\n      value = \"\";\n      for (var i = 0; i < str.length; i++) {\n        var code = str.charCodeAt(i);\n        if (str[i].search(numberingSystems.hanidec) !== -1) {\n          value += hanidecChars.indexOf(str[i]);\n        } else {\n          for (var key in numberingSystemsUTF16) {\n            var _numberingSystemsUTF = numberingSystemsUTF16[key],\n              min = _numberingSystemsUTF[0],\n              max = _numberingSystemsUTF[1];\n            if (code >= min && code <= max) {\n              value += code - min;\n            }\n          }\n        }\n      }\n      return parseInt(value, 10);\n    } else {\n      return value;\n    }\n  }\n\n  // cache of {numberingSystem: {append: regex}}\n  var digitRegexCache = {};\n  function resetDigitRegexCache() {\n    digitRegexCache = {};\n  }\n  function digitRegex(_ref, append) {\n    var numberingSystem = _ref.numberingSystem;\n    if (append === void 0) {\n      append = \"\";\n    }\n    var ns = numberingSystem || \"latn\";\n    if (!digitRegexCache[ns]) {\n      digitRegexCache[ns] = {};\n    }\n    if (!digitRegexCache[ns][append]) {\n      digitRegexCache[ns][append] = new RegExp(\"\" + numberingSystems[ns] + append);\n    }\n    return digitRegexCache[ns][append];\n  }\n\n  var now = function now() {\n      return Date.now();\n    },\n    defaultZone = \"system\",\n    defaultLocale = null,\n    defaultNumberingSystem = null,\n    defaultOutputCalendar = null,\n    twoDigitCutoffYear = 60,\n    throwOnInvalid,\n    defaultWeekSettings = null;\n\n  /**\n   * Settings contains static getters and setters that control Luxon's overall behavior. Luxon is a simple library with few options, but the ones it does have live here.\n   */\n  var Settings = /*#__PURE__*/function () {\n    function Settings() {}\n    /**\n     * Reset Luxon's global caches. Should only be necessary in testing scenarios.\n     * @return {void}\n     */\n    Settings.resetCaches = function resetCaches() {\n      Locale.resetCache();\n      IANAZone.resetCache();\n      DateTime.resetCache();\n      resetDigitRegexCache();\n    };\n    _createClass(Settings, null, [{\n      key: \"now\",\n      get:\n      /**\n       * Get the callback for returning the current timestamp.\n       * @type {function}\n       */\n      function get() {\n        return now;\n      }\n\n      /**\n       * Set the callback for returning the current timestamp.\n       * The function should return a number, which will be interpreted as an Epoch millisecond count\n       * @type {function}\n       * @example Settings.now = () => Date.now() + 3000 // pretend it is 3 seconds in the future\n       * @example Settings.now = () => 0 // always pretend it's Jan 1, 1970 at midnight in UTC time\n       */,\n      set: function set(n) {\n        now = n;\n      }\n\n      /**\n       * Set the default time zone to create DateTimes in. Does not affect existing instances.\n       * Use the value \"system\" to reset this value to the system's time zone.\n       * @type {string}\n       */\n    }, {\n      key: \"defaultZone\",\n      get:\n      /**\n       * Get the default time zone object currently used to create DateTimes. Does not affect existing instances.\n       * The default value is the system's time zone (the one set on the machine that runs this code).\n       * @type {Zone}\n       */\n      function get() {\n        return normalizeZone(defaultZone, SystemZone.instance);\n      }\n\n      /**\n       * Get the default locale to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */,\n      set: function set(zone) {\n        defaultZone = zone;\n      }\n    }, {\n      key: \"defaultLocale\",\n      get: function get() {\n        return defaultLocale;\n      }\n\n      /**\n       * Set the default locale to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */,\n      set: function set(locale) {\n        defaultLocale = locale;\n      }\n\n      /**\n       * Get the default numbering system to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n    }, {\n      key: \"defaultNumberingSystem\",\n      get: function get() {\n        return defaultNumberingSystem;\n      }\n\n      /**\n       * Set the default numbering system to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */,\n      set: function set(numberingSystem) {\n        defaultNumberingSystem = numberingSystem;\n      }\n\n      /**\n       * Get the default output calendar to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n    }, {\n      key: \"defaultOutputCalendar\",\n      get: function get() {\n        return defaultOutputCalendar;\n      }\n\n      /**\n       * Set the default output calendar to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */,\n      set: function set(outputCalendar) {\n        defaultOutputCalendar = outputCalendar;\n      }\n\n      /**\n       * @typedef {Object} WeekSettings\n       * @property {number} firstDay\n       * @property {number} minimalDays\n       * @property {number[]} weekend\n       */\n\n      /**\n       * @return {WeekSettings|null}\n       */\n    }, {\n      key: \"defaultWeekSettings\",\n      get: function get() {\n        return defaultWeekSettings;\n      }\n\n      /**\n       * Allows overriding the default locale week settings, i.e. the start of the week, the weekend and\n       * how many days are required in the first week of a year.\n       * Does not affect existing instances.\n       *\n       * @param {WeekSettings|null} weekSettings\n       */,\n      set: function set(weekSettings) {\n        defaultWeekSettings = validateWeekSettings(weekSettings);\n      }\n\n      /**\n       * Get the cutoff year for whether a 2-digit year string is interpreted in the current or previous century. Numbers higher than the cutoff will be considered to mean 19xx and numbers lower or equal to the cutoff will be considered 20xx.\n       * @type {number}\n       */\n    }, {\n      key: \"twoDigitCutoffYear\",\n      get: function get() {\n        return twoDigitCutoffYear;\n      }\n\n      /**\n       * Set the cutoff year for whether a 2-digit year string is interpreted in the current or previous century. Numbers higher than the cutoff will be considered to mean 19xx and numbers lower or equal to the cutoff will be considered 20xx.\n       * @type {number}\n       * @example Settings.twoDigitCutoffYear = 0 // all 'yy' are interpreted as 20th century\n       * @example Settings.twoDigitCutoffYear = 99 // all 'yy' are interpreted as 21st century\n       * @example Settings.twoDigitCutoffYear = 50 // '49' -> 2049; '50' -> 1950\n       * @example Settings.twoDigitCutoffYear = 1950 // interpreted as 50\n       * @example Settings.twoDigitCutoffYear = 2050 // ALSO interpreted as 50\n       */,\n      set: function set(cutoffYear) {\n        twoDigitCutoffYear = cutoffYear % 100;\n      }\n\n      /**\n       * Get whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals\n       * @type {boolean}\n       */\n    }, {\n      key: \"throwOnInvalid\",\n      get: function get() {\n        return throwOnInvalid;\n      }\n\n      /**\n       * Set whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals\n       * @type {boolean}\n       */,\n      set: function set(t) {\n        throwOnInvalid = t;\n      }\n    }]);\n    return Settings;\n  }();\n\n  var Invalid = /*#__PURE__*/function () {\n    function Invalid(reason, explanation) {\n      this.reason = reason;\n      this.explanation = explanation;\n    }\n    var _proto = Invalid.prototype;\n    _proto.toMessage = function toMessage() {\n      if (this.explanation) {\n        return this.reason + \": \" + this.explanation;\n      } else {\n        return this.reason;\n      }\n    };\n    return Invalid;\n  }();\n\n  var nonLeapLadder = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],\n    leapLadder = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];\n  function unitOutOfRange(unit, value) {\n    return new Invalid(\"unit out of range\", \"you specified \" + value + \" (of type \" + typeof value + \") as a \" + unit + \", which is invalid\");\n  }\n  function dayOfWeek(year, month, day) {\n    var d = new Date(Date.UTC(year, month - 1, day));\n    if (year < 100 && year >= 0) {\n      d.setUTCFullYear(d.getUTCFullYear() - 1900);\n    }\n    var js = d.getUTCDay();\n    return js === 0 ? 7 : js;\n  }\n  function computeOrdinal(year, month, day) {\n    return day + (isLeapYear(year) ? leapLadder : nonLeapLadder)[month - 1];\n  }\n  function uncomputeOrdinal(year, ordinal) {\n    var table = isLeapYear(year) ? leapLadder : nonLeapLadder,\n      month0 = table.findIndex(function (i) {\n        return i < ordinal;\n      }),\n      day = ordinal - table[month0];\n    return {\n      month: month0 + 1,\n      day: day\n    };\n  }\n  function isoWeekdayToLocal(isoWeekday, startOfWeek) {\n    return (isoWeekday - startOfWeek + 7) % 7 + 1;\n  }\n\n  /**\n   * @private\n   */\n\n  function gregorianToWeek(gregObj, minDaysInFirstWeek, startOfWeek) {\n    if (minDaysInFirstWeek === void 0) {\n      minDaysInFirstWeek = 4;\n    }\n    if (startOfWeek === void 0) {\n      startOfWeek = 1;\n    }\n    var year = gregObj.year,\n      month = gregObj.month,\n      day = gregObj.day,\n      ordinal = computeOrdinal(year, month, day),\n      weekday = isoWeekdayToLocal(dayOfWeek(year, month, day), startOfWeek);\n    var weekNumber = Math.floor((ordinal - weekday + 14 - minDaysInFirstWeek) / 7),\n      weekYear;\n    if (weekNumber < 1) {\n      weekYear = year - 1;\n      weekNumber = weeksInWeekYear(weekYear, minDaysInFirstWeek, startOfWeek);\n    } else if (weekNumber > weeksInWeekYear(year, minDaysInFirstWeek, startOfWeek)) {\n      weekYear = year + 1;\n      weekNumber = 1;\n    } else {\n      weekYear = year;\n    }\n    return _extends({\n      weekYear: weekYear,\n      weekNumber: weekNumber,\n      weekday: weekday\n    }, timeObject(gregObj));\n  }\n  function weekToGregorian(weekData, minDaysInFirstWeek, startOfWeek) {\n    if (minDaysInFirstWeek === void 0) {\n      minDaysInFirstWeek = 4;\n    }\n    if (startOfWeek === void 0) {\n      startOfWeek = 1;\n    }\n    var weekYear = weekData.weekYear,\n      weekNumber = weekData.weekNumber,\n      weekday = weekData.weekday,\n      weekdayOfJan4 = isoWeekdayToLocal(dayOfWeek(weekYear, 1, minDaysInFirstWeek), startOfWeek),\n      yearInDays = daysInYear(weekYear);\n    var ordinal = weekNumber * 7 + weekday - weekdayOfJan4 - 7 + minDaysInFirstWeek,\n      year;\n    if (ordinal < 1) {\n      year = weekYear - 1;\n      ordinal += daysInYear(year);\n    } else if (ordinal > yearInDays) {\n      year = weekYear + 1;\n      ordinal -= daysInYear(weekYear);\n    } else {\n      year = weekYear;\n    }\n    var _uncomputeOrdinal = uncomputeOrdinal(year, ordinal),\n      month = _uncomputeOrdinal.month,\n      day = _uncomputeOrdinal.day;\n    return _extends({\n      year: year,\n      month: month,\n      day: day\n    }, timeObject(weekData));\n  }\n  function gregorianToOrdinal(gregData) {\n    var year = gregData.year,\n      month = gregData.month,\n      day = gregData.day;\n    var ordinal = computeOrdinal(year, month, day);\n    return _extends({\n      year: year,\n      ordinal: ordinal\n    }, timeObject(gregData));\n  }\n  function ordinalToGregorian(ordinalData) {\n    var year = ordinalData.year,\n      ordinal = ordinalData.ordinal;\n    var _uncomputeOrdinal2 = uncomputeOrdinal(year, ordinal),\n      month = _uncomputeOrdinal2.month,\n      day = _uncomputeOrdinal2.day;\n    return _extends({\n      year: year,\n      month: month,\n      day: day\n    }, timeObject(ordinalData));\n  }\n\n  /**\n   * Check if local week units like localWeekday are used in obj.\n   * If so, validates that they are not mixed with ISO week units and then copies them to the normal week unit properties.\n   * Modifies obj in-place!\n   * @param obj the object values\n   */\n  function usesLocalWeekValues(obj, loc) {\n    var hasLocaleWeekData = !isUndefined(obj.localWeekday) || !isUndefined(obj.localWeekNumber) || !isUndefined(obj.localWeekYear);\n    if (hasLocaleWeekData) {\n      var hasIsoWeekData = !isUndefined(obj.weekday) || !isUndefined(obj.weekNumber) || !isUndefined(obj.weekYear);\n      if (hasIsoWeekData) {\n        throw new ConflictingSpecificationError(\"Cannot mix locale-based week fields with ISO-based week fields\");\n      }\n      if (!isUndefined(obj.localWeekday)) obj.weekday = obj.localWeekday;\n      if (!isUndefined(obj.localWeekNumber)) obj.weekNumber = obj.localWeekNumber;\n      if (!isUndefined(obj.localWeekYear)) obj.weekYear = obj.localWeekYear;\n      delete obj.localWeekday;\n      delete obj.localWeekNumber;\n      delete obj.localWeekYear;\n      return {\n        minDaysInFirstWeek: loc.getMinDaysInFirstWeek(),\n        startOfWeek: loc.getStartOfWeek()\n      };\n    } else {\n      return {\n        minDaysInFirstWeek: 4,\n        startOfWeek: 1\n      };\n    }\n  }\n  function hasInvalidWeekData(obj, minDaysInFirstWeek, startOfWeek) {\n    if (minDaysInFirstWeek === void 0) {\n      minDaysInFirstWeek = 4;\n    }\n    if (startOfWeek === void 0) {\n      startOfWeek = 1;\n    }\n    var validYear = isInteger(obj.weekYear),\n      validWeek = integerBetween(obj.weekNumber, 1, weeksInWeekYear(obj.weekYear, minDaysInFirstWeek, startOfWeek)),\n      validWeekday = integerBetween(obj.weekday, 1, 7);\n    if (!validYear) {\n      return unitOutOfRange(\"weekYear\", obj.weekYear);\n    } else if (!validWeek) {\n      return unitOutOfRange(\"week\", obj.weekNumber);\n    } else if (!validWeekday) {\n      return unitOutOfRange(\"weekday\", obj.weekday);\n    } else return false;\n  }\n  function hasInvalidOrdinalData(obj) {\n    var validYear = isInteger(obj.year),\n      validOrdinal = integerBetween(obj.ordinal, 1, daysInYear(obj.year));\n    if (!validYear) {\n      return unitOutOfRange(\"year\", obj.year);\n    } else if (!validOrdinal) {\n      return unitOutOfRange(\"ordinal\", obj.ordinal);\n    } else return false;\n  }\n  function hasInvalidGregorianData(obj) {\n    var validYear = isInteger(obj.year),\n      validMonth = integerBetween(obj.month, 1, 12),\n      validDay = integerBetween(obj.day, 1, daysInMonth(obj.year, obj.month));\n    if (!validYear) {\n      return unitOutOfRange(\"year\", obj.year);\n    } else if (!validMonth) {\n      return unitOutOfRange(\"month\", obj.month);\n    } else if (!validDay) {\n      return unitOutOfRange(\"day\", obj.day);\n    } else return false;\n  }\n  function hasInvalidTimeData(obj) {\n    var hour = obj.hour,\n      minute = obj.minute,\n      second = obj.second,\n      millisecond = obj.millisecond;\n    var validHour = integerBetween(hour, 0, 23) || hour === 24 && minute === 0 && second === 0 && millisecond === 0,\n      validMinute = integerBetween(minute, 0, 59),\n      validSecond = integerBetween(second, 0, 59),\n      validMillisecond = integerBetween(millisecond, 0, 999);\n    if (!validHour) {\n      return unitOutOfRange(\"hour\", hour);\n    } else if (!validMinute) {\n      return unitOutOfRange(\"minute\", minute);\n    } else if (!validSecond) {\n      return unitOutOfRange(\"second\", second);\n    } else if (!validMillisecond) {\n      return unitOutOfRange(\"millisecond\", millisecond);\n    } else return false;\n  }\n\n  /**\n   * @private\n   */\n\n  // TYPES\n\n  function isUndefined(o) {\n    return typeof o === \"undefined\";\n  }\n  function isNumber(o) {\n    return typeof o === \"number\";\n  }\n  function isInteger(o) {\n    return typeof o === \"number\" && o % 1 === 0;\n  }\n  function isString(o) {\n    return typeof o === \"string\";\n  }\n  function isDate(o) {\n    return Object.prototype.toString.call(o) === \"[object Date]\";\n  }\n\n  // CAPABILITIES\n\n  function hasRelative() {\n    try {\n      return typeof Intl !== \"undefined\" && !!Intl.RelativeTimeFormat;\n    } catch (e) {\n      return false;\n    }\n  }\n  function hasLocaleWeekInfo() {\n    try {\n      return typeof Intl !== \"undefined\" && !!Intl.Locale && (\"weekInfo\" in Intl.Locale.prototype || \"getWeekInfo\" in Intl.Locale.prototype);\n    } catch (e) {\n      return false;\n    }\n  }\n\n  // OBJECTS AND ARRAYS\n\n  function maybeArray(thing) {\n    return Array.isArray(thing) ? thing : [thing];\n  }\n  function bestBy(arr, by, compare) {\n    if (arr.length === 0) {\n      return undefined;\n    }\n    return arr.reduce(function (best, next) {\n      var pair = [by(next), next];\n      if (!best) {\n        return pair;\n      } else if (compare(best[0], pair[0]) === best[0]) {\n        return best;\n      } else {\n        return pair;\n      }\n    }, null)[1];\n  }\n  function pick(obj, keys) {\n    return keys.reduce(function (a, k) {\n      a[k] = obj[k];\n      return a;\n    }, {});\n  }\n  function hasOwnProperty(obj, prop) {\n    return Object.prototype.hasOwnProperty.call(obj, prop);\n  }\n  function validateWeekSettings(settings) {\n    if (settings == null) {\n      return null;\n    } else if (typeof settings !== \"object\") {\n      throw new InvalidArgumentError(\"Week settings must be an object\");\n    } else {\n      if (!integerBetween(settings.firstDay, 1, 7) || !integerBetween(settings.minimalDays, 1, 7) || !Array.isArray(settings.weekend) || settings.weekend.some(function (v) {\n        return !integerBetween(v, 1, 7);\n      })) {\n        throw new InvalidArgumentError(\"Invalid week settings\");\n      }\n      return {\n        firstDay: settings.firstDay,\n        minimalDays: settings.minimalDays,\n        weekend: Array.from(settings.weekend)\n      };\n    }\n  }\n\n  // NUMBERS AND STRINGS\n\n  function integerBetween(thing, bottom, top) {\n    return isInteger(thing) && thing >= bottom && thing <= top;\n  }\n\n  // x % n but takes the sign of n instead of x\n  function floorMod(x, n) {\n    return x - n * Math.floor(x / n);\n  }\n  function padStart(input, n) {\n    if (n === void 0) {\n      n = 2;\n    }\n    var isNeg = input < 0;\n    var padded;\n    if (isNeg) {\n      padded = \"-\" + (\"\" + -input).padStart(n, \"0\");\n    } else {\n      padded = (\"\" + input).padStart(n, \"0\");\n    }\n    return padded;\n  }\n  function parseInteger(string) {\n    if (isUndefined(string) || string === null || string === \"\") {\n      return undefined;\n    } else {\n      return parseInt(string, 10);\n    }\n  }\n  function parseFloating(string) {\n    if (isUndefined(string) || string === null || string === \"\") {\n      return undefined;\n    } else {\n      return parseFloat(string);\n    }\n  }\n  function parseMillis(fraction) {\n    // Return undefined (instead of 0) in these cases, where fraction is not set\n    if (isUndefined(fraction) || fraction === null || fraction === \"\") {\n      return undefined;\n    } else {\n      var f = parseFloat(\"0.\" + fraction) * 1000;\n      return Math.floor(f);\n    }\n  }\n  function roundTo(number, digits, towardZero) {\n    if (towardZero === void 0) {\n      towardZero = false;\n    }\n    var factor = Math.pow(10, digits),\n      rounder = towardZero ? Math.trunc : Math.round;\n    return rounder(number * factor) / factor;\n  }\n\n  // DATE BASICS\n\n  function isLeapYear(year) {\n    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n  }\n  function daysInYear(year) {\n    return isLeapYear(year) ? 366 : 365;\n  }\n  function daysInMonth(year, month) {\n    var modMonth = floorMod(month - 1, 12) + 1,\n      modYear = year + (month - modMonth) / 12;\n    if (modMonth === 2) {\n      return isLeapYear(modYear) ? 29 : 28;\n    } else {\n      return [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][modMonth - 1];\n    }\n  }\n\n  // convert a calendar object to a local timestamp (epoch, but with the offset baked in)\n  function objToLocalTS(obj) {\n    var d = Date.UTC(obj.year, obj.month - 1, obj.day, obj.hour, obj.minute, obj.second, obj.millisecond);\n\n    // for legacy reasons, years between 0 and 99 are interpreted as 19XX; revert that\n    if (obj.year < 100 && obj.year >= 0) {\n      d = new Date(d);\n      // set the month and day again, this is necessary because year 2000 is a leap year, but year 100 is not\n      // so if obj.year is in 99, but obj.day makes it roll over into year 100,\n      // the calculations done by Date.UTC are using year 2000 - which is incorrect\n      d.setUTCFullYear(obj.year, obj.month - 1, obj.day);\n    }\n    return +d;\n  }\n\n  // adapted from moment.js: https://github.com/moment/moment/blob/000ac1800e620f770f4eb31b5ae908f6167b0ab2/src/lib/units/week-calendar-utils.js\n  function firstWeekOffset(year, minDaysInFirstWeek, startOfWeek) {\n    var fwdlw = isoWeekdayToLocal(dayOfWeek(year, 1, minDaysInFirstWeek), startOfWeek);\n    return -fwdlw + minDaysInFirstWeek - 1;\n  }\n  function weeksInWeekYear(weekYear, minDaysInFirstWeek, startOfWeek) {\n    if (minDaysInFirstWeek === void 0) {\n      minDaysInFirstWeek = 4;\n    }\n    if (startOfWeek === void 0) {\n      startOfWeek = 1;\n    }\n    var weekOffset = firstWeekOffset(weekYear, minDaysInFirstWeek, startOfWeek);\n    var weekOffsetNext = firstWeekOffset(weekYear + 1, minDaysInFirstWeek, startOfWeek);\n    return (daysInYear(weekYear) - weekOffset + weekOffsetNext) / 7;\n  }\n  function untruncateYear(year) {\n    if (year > 99) {\n      return year;\n    } else return year > Settings.twoDigitCutoffYear ? 1900 + year : 2000 + year;\n  }\n\n  // PARSING\n\n  function parseZoneInfo(ts, offsetFormat, locale, timeZone) {\n    if (timeZone === void 0) {\n      timeZone = null;\n    }\n    var date = new Date(ts),\n      intlOpts = {\n        hourCycle: \"h23\",\n        year: \"numeric\",\n        month: \"2-digit\",\n        day: \"2-digit\",\n        hour: \"2-digit\",\n        minute: \"2-digit\"\n      };\n    if (timeZone) {\n      intlOpts.timeZone = timeZone;\n    }\n    var modified = _extends({\n      timeZoneName: offsetFormat\n    }, intlOpts);\n    var parsed = new Intl.DateTimeFormat(locale, modified).formatToParts(date).find(function (m) {\n      return m.type.toLowerCase() === \"timezonename\";\n    });\n    return parsed ? parsed.value : null;\n  }\n\n  // signedOffset('-5', '30') -> -330\n  function signedOffset(offHourStr, offMinuteStr) {\n    var offHour = parseInt(offHourStr, 10);\n\n    // don't || this because we want to preserve -0\n    if (Number.isNaN(offHour)) {\n      offHour = 0;\n    }\n    var offMin = parseInt(offMinuteStr, 10) || 0,\n      offMinSigned = offHour < 0 || Object.is(offHour, -0) ? -offMin : offMin;\n    return offHour * 60 + offMinSigned;\n  }\n\n  // COERCION\n\n  function asNumber(value) {\n    var numericValue = Number(value);\n    if (typeof value === \"boolean\" || value === \"\" || Number.isNaN(numericValue)) throw new InvalidArgumentError(\"Invalid unit value \" + value);\n    return numericValue;\n  }\n  function normalizeObject(obj, normalizer) {\n    var normalized = {};\n    for (var u in obj) {\n      if (hasOwnProperty(obj, u)) {\n        var v = obj[u];\n        if (v === undefined || v === null) continue;\n        normalized[normalizer(u)] = asNumber(v);\n      }\n    }\n    return normalized;\n  }\n\n  /**\n   * Returns the offset's value as a string\n   * @param {number} ts - Epoch milliseconds for which to get the offset\n   * @param {string} format - What style of offset to return.\n   *                          Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively\n   * @return {string}\n   */\n  function formatOffset(offset, format) {\n    var hours = Math.trunc(Math.abs(offset / 60)),\n      minutes = Math.trunc(Math.abs(offset % 60)),\n      sign = offset >= 0 ? \"+\" : \"-\";\n    switch (format) {\n      case \"short\":\n        return \"\" + sign + padStart(hours, 2) + \":\" + padStart(minutes, 2);\n      case \"narrow\":\n        return \"\" + sign + hours + (minutes > 0 ? \":\" + minutes : \"\");\n      case \"techie\":\n        return \"\" + sign + padStart(hours, 2) + padStart(minutes, 2);\n      default:\n        throw new RangeError(\"Value format \" + format + \" is out of range for property format\");\n    }\n  }\n  function timeObject(obj) {\n    return pick(obj, [\"hour\", \"minute\", \"second\", \"millisecond\"]);\n  }\n\n  /**\n   * @private\n   */\n\n  var monthsLong = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\n  var monthsShort = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n  var monthsNarrow = [\"J\", \"F\", \"M\", \"A\", \"M\", \"J\", \"J\", \"A\", \"S\", \"O\", \"N\", \"D\"];\n  function months(length) {\n    switch (length) {\n      case \"narrow\":\n        return [].concat(monthsNarrow);\n      case \"short\":\n        return [].concat(monthsShort);\n      case \"long\":\n        return [].concat(monthsLong);\n      case \"numeric\":\n        return [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\"];\n      case \"2-digit\":\n        return [\"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\"];\n      default:\n        return null;\n    }\n  }\n  var weekdaysLong = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"];\n  var weekdaysShort = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"];\n  var weekdaysNarrow = [\"M\", \"T\", \"W\", \"T\", \"F\", \"S\", \"S\"];\n  function weekdays(length) {\n    switch (length) {\n      case \"narrow\":\n        return [].concat(weekdaysNarrow);\n      case \"short\":\n        return [].concat(weekdaysShort);\n      case \"long\":\n        return [].concat(weekdaysLong);\n      case \"numeric\":\n        return [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\"];\n      default:\n        return null;\n    }\n  }\n  var meridiems = [\"AM\", \"PM\"];\n  var erasLong = [\"Before Christ\", \"Anno Domini\"];\n  var erasShort = [\"BC\", \"AD\"];\n  var erasNarrow = [\"B\", \"A\"];\n  function eras(length) {\n    switch (length) {\n      case \"narrow\":\n        return [].concat(erasNarrow);\n      case \"short\":\n        return [].concat(erasShort);\n      case \"long\":\n        return [].concat(erasLong);\n      default:\n        return null;\n    }\n  }\n  function meridiemForDateTime(dt) {\n    return meridiems[dt.hour < 12 ? 0 : 1];\n  }\n  function weekdayForDateTime(dt, length) {\n    return weekdays(length)[dt.weekday - 1];\n  }\n  function monthForDateTime(dt, length) {\n    return months(length)[dt.month - 1];\n  }\n  function eraForDateTime(dt, length) {\n    return eras(length)[dt.year < 0 ? 0 : 1];\n  }\n  function formatRelativeTime(unit, count, numeric, narrow) {\n    if (numeric === void 0) {\n      numeric = \"always\";\n    }\n    if (narrow === void 0) {\n      narrow = false;\n    }\n    var units = {\n      years: [\"year\", \"yr.\"],\n      quarters: [\"quarter\", \"qtr.\"],\n      months: [\"month\", \"mo.\"],\n      weeks: [\"week\", \"wk.\"],\n      days: [\"day\", \"day\", \"days\"],\n      hours: [\"hour\", \"hr.\"],\n      minutes: [\"minute\", \"min.\"],\n      seconds: [\"second\", \"sec.\"]\n    };\n    var lastable = [\"hours\", \"minutes\", \"seconds\"].indexOf(unit) === -1;\n    if (numeric === \"auto\" && lastable) {\n      var isDay = unit === \"days\";\n      switch (count) {\n        case 1:\n          return isDay ? \"tomorrow\" : \"next \" + units[unit][0];\n        case -1:\n          return isDay ? \"yesterday\" : \"last \" + units[unit][0];\n        case 0:\n          return isDay ? \"today\" : \"this \" + units[unit][0];\n      }\n    }\n\n    var isInPast = Object.is(count, -0) || count < 0,\n      fmtValue = Math.abs(count),\n      singular = fmtValue === 1,\n      lilUnits = units[unit],\n      fmtUnit = narrow ? singular ? lilUnits[1] : lilUnits[2] || lilUnits[1] : singular ? units[unit][0] : unit;\n    return isInPast ? fmtValue + \" \" + fmtUnit + \" ago\" : \"in \" + fmtValue + \" \" + fmtUnit;\n  }\n\n  function stringifyTokens(splits, tokenToString) {\n    var s = \"\";\n    for (var _iterator = _createForOfIteratorHelperLoose(splits), _step; !(_step = _iterator()).done;) {\n      var token = _step.value;\n      if (token.literal) {\n        s += token.val;\n      } else {\n        s += tokenToString(token.val);\n      }\n    }\n    return s;\n  }\n  var _macroTokenToFormatOpts = {\n    D: DATE_SHORT,\n    DD: DATE_MED,\n    DDD: DATE_FULL,\n    DDDD: DATE_HUGE,\n    t: TIME_SIMPLE,\n    tt: TIME_WITH_SECONDS,\n    ttt: TIME_WITH_SHORT_OFFSET,\n    tttt: TIME_WITH_LONG_OFFSET,\n    T: TIME_24_SIMPLE,\n    TT: TIME_24_WITH_SECONDS,\n    TTT: TIME_24_WITH_SHORT_OFFSET,\n    TTTT: TIME_24_WITH_LONG_OFFSET,\n    f: DATETIME_SHORT,\n    ff: DATETIME_MED,\n    fff: DATETIME_FULL,\n    ffff: DATETIME_HUGE,\n    F: DATETIME_SHORT_WITH_SECONDS,\n    FF: DATETIME_MED_WITH_SECONDS,\n    FFF: DATETIME_FULL_WITH_SECONDS,\n    FFFF: DATETIME_HUGE_WITH_SECONDS\n  };\n\n  /**\n   * @private\n   */\n  var Formatter = /*#__PURE__*/function () {\n    Formatter.create = function create(locale, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return new Formatter(locale, opts);\n    };\n    Formatter.parseFormat = function parseFormat(fmt) {\n      // white-space is always considered a literal in user-provided formats\n      // the \" \" token has a special meaning (see unitForToken)\n\n      var current = null,\n        currentFull = \"\",\n        bracketed = false;\n      var splits = [];\n      for (var i = 0; i < fmt.length; i++) {\n        var c = fmt.charAt(i);\n        if (c === \"'\") {\n          if (currentFull.length > 0) {\n            splits.push({\n              literal: bracketed || /^\\s+$/.test(currentFull),\n              val: currentFull\n            });\n          }\n          current = null;\n          currentFull = \"\";\n          bracketed = !bracketed;\n        } else if (bracketed) {\n          currentFull += c;\n        } else if (c === current) {\n          currentFull += c;\n        } else {\n          if (currentFull.length > 0) {\n            splits.push({\n              literal: /^\\s+$/.test(currentFull),\n              val: currentFull\n            });\n          }\n          currentFull = c;\n          current = c;\n        }\n      }\n      if (currentFull.length > 0) {\n        splits.push({\n          literal: bracketed || /^\\s+$/.test(currentFull),\n          val: currentFull\n        });\n      }\n      return splits;\n    };\n    Formatter.macroTokenToFormatOpts = function macroTokenToFormatOpts(token) {\n      return _macroTokenToFormatOpts[token];\n    };\n    function Formatter(locale, formatOpts) {\n      this.opts = formatOpts;\n      this.loc = locale;\n      this.systemLoc = null;\n    }\n    var _proto = Formatter.prototype;\n    _proto.formatWithSystemDefault = function formatWithSystemDefault(dt, opts) {\n      if (this.systemLoc === null) {\n        this.systemLoc = this.loc.redefaultToSystem();\n      }\n      var df = this.systemLoc.dtFormatter(dt, _extends({}, this.opts, opts));\n      return df.format();\n    };\n    _proto.dtFormatter = function dtFormatter(dt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.loc.dtFormatter(dt, _extends({}, this.opts, opts));\n    };\n    _proto.formatDateTime = function formatDateTime(dt, opts) {\n      return this.dtFormatter(dt, opts).format();\n    };\n    _proto.formatDateTimeParts = function formatDateTimeParts(dt, opts) {\n      return this.dtFormatter(dt, opts).formatToParts();\n    };\n    _proto.formatInterval = function formatInterval(interval, opts) {\n      var df = this.dtFormatter(interval.start, opts);\n      return df.dtf.formatRange(interval.start.toJSDate(), interval.end.toJSDate());\n    };\n    _proto.resolvedOptions = function resolvedOptions(dt, opts) {\n      return this.dtFormatter(dt, opts).resolvedOptions();\n    };\n    _proto.num = function num(n, p) {\n      if (p === void 0) {\n        p = 0;\n      }\n      // we get some perf out of doing this here, annoyingly\n      if (this.opts.forceSimple) {\n        return padStart(n, p);\n      }\n      var opts = _extends({}, this.opts);\n      if (p > 0) {\n        opts.padTo = p;\n      }\n      return this.loc.numberFormatter(opts).format(n);\n    };\n    _proto.formatDateTimeFromString = function formatDateTimeFromString(dt, fmt) {\n      var _this = this;\n      var knownEnglish = this.loc.listingMode() === \"en\",\n        useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== \"gregory\",\n        string = function string(opts, extract) {\n          return _this.loc.extract(dt, opts, extract);\n        },\n        formatOffset = function formatOffset(opts) {\n          if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) {\n            return \"Z\";\n          }\n          return dt.isValid ? dt.zone.formatOffset(dt.ts, opts.format) : \"\";\n        },\n        meridiem = function meridiem() {\n          return knownEnglish ? meridiemForDateTime(dt) : string({\n            hour: \"numeric\",\n            hourCycle: \"h12\"\n          }, \"dayperiod\");\n        },\n        month = function month(length, standalone) {\n          return knownEnglish ? monthForDateTime(dt, length) : string(standalone ? {\n            month: length\n          } : {\n            month: length,\n            day: \"numeric\"\n          }, \"month\");\n        },\n        weekday = function weekday(length, standalone) {\n          return knownEnglish ? weekdayForDateTime(dt, length) : string(standalone ? {\n            weekday: length\n          } : {\n            weekday: length,\n            month: \"long\",\n            day: \"numeric\"\n          }, \"weekday\");\n        },\n        maybeMacro = function maybeMacro(token) {\n          var formatOpts = Formatter.macroTokenToFormatOpts(token);\n          if (formatOpts) {\n            return _this.formatWithSystemDefault(dt, formatOpts);\n          } else {\n            return token;\n          }\n        },\n        era = function era(length) {\n          return knownEnglish ? eraForDateTime(dt, length) : string({\n            era: length\n          }, \"era\");\n        },\n        tokenToString = function tokenToString(token) {\n          // Where possible: https://cldr.unicode.org/translation/date-time/date-time-symbols\n          switch (token) {\n            // ms\n            case \"S\":\n              return _this.num(dt.millisecond);\n            case \"u\":\n            // falls through\n            case \"SSS\":\n              return _this.num(dt.millisecond, 3);\n            // seconds\n            case \"s\":\n              return _this.num(dt.second);\n            case \"ss\":\n              return _this.num(dt.second, 2);\n            // fractional seconds\n            case \"uu\":\n              return _this.num(Math.floor(dt.millisecond / 10), 2);\n            case \"uuu\":\n              return _this.num(Math.floor(dt.millisecond / 100));\n            // minutes\n            case \"m\":\n              return _this.num(dt.minute);\n            case \"mm\":\n              return _this.num(dt.minute, 2);\n            // hours\n            case \"h\":\n              return _this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12);\n            case \"hh\":\n              return _this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12, 2);\n            case \"H\":\n              return _this.num(dt.hour);\n            case \"HH\":\n              return _this.num(dt.hour, 2);\n            // offset\n            case \"Z\":\n              // like +6\n              return formatOffset({\n                format: \"narrow\",\n                allowZ: _this.opts.allowZ\n              });\n            case \"ZZ\":\n              // like +06:00\n              return formatOffset({\n                format: \"short\",\n                allowZ: _this.opts.allowZ\n              });\n            case \"ZZZ\":\n              // like +0600\n              return formatOffset({\n                format: \"techie\",\n                allowZ: _this.opts.allowZ\n              });\n            case \"ZZZZ\":\n              // like EST\n              return dt.zone.offsetName(dt.ts, {\n                format: \"short\",\n                locale: _this.loc.locale\n              });\n            case \"ZZZZZ\":\n              // like Eastern Standard Time\n              return dt.zone.offsetName(dt.ts, {\n                format: \"long\",\n                locale: _this.loc.locale\n              });\n            // zone\n            case \"z\":\n              // like America/New_York\n              return dt.zoneName;\n            // meridiems\n            case \"a\":\n              return meridiem();\n            // dates\n            case \"d\":\n              return useDateTimeFormatter ? string({\n                day: \"numeric\"\n              }, \"day\") : _this.num(dt.day);\n            case \"dd\":\n              return useDateTimeFormatter ? string({\n                day: \"2-digit\"\n              }, \"day\") : _this.num(dt.day, 2);\n            // weekdays - standalone\n            case \"c\":\n              // like 1\n              return _this.num(dt.weekday);\n            case \"ccc\":\n              // like 'Tues'\n              return weekday(\"short\", true);\n            case \"cccc\":\n              // like 'Tuesday'\n              return weekday(\"long\", true);\n            case \"ccccc\":\n              // like 'T'\n              return weekday(\"narrow\", true);\n            // weekdays - format\n            case \"E\":\n              // like 1\n              return _this.num(dt.weekday);\n            case \"EEE\":\n              // like 'Tues'\n              return weekday(\"short\", false);\n            case \"EEEE\":\n              // like 'Tuesday'\n              return weekday(\"long\", false);\n            case \"EEEEE\":\n              // like 'T'\n              return weekday(\"narrow\", false);\n            // months - standalone\n            case \"L\":\n              // like 1\n              return useDateTimeFormatter ? string({\n                month: \"numeric\",\n                day: \"numeric\"\n              }, \"month\") : _this.num(dt.month);\n            case \"LL\":\n              // like 01, doesn't seem to work\n              return useDateTimeFormatter ? string({\n                month: \"2-digit\",\n                day: \"numeric\"\n              }, \"month\") : _this.num(dt.month, 2);\n            case \"LLL\":\n              // like Jan\n              return month(\"short\", true);\n            case \"LLLL\":\n              // like January\n              return month(\"long\", true);\n            case \"LLLLL\":\n              // like J\n              return month(\"narrow\", true);\n            // months - format\n            case \"M\":\n              // like 1\n              return useDateTimeFormatter ? string({\n                month: \"numeric\"\n              }, \"month\") : _this.num(dt.month);\n            case \"MM\":\n              // like 01\n              return useDateTimeFormatter ? string({\n                month: \"2-digit\"\n              }, \"month\") : _this.num(dt.month, 2);\n            case \"MMM\":\n              // like Jan\n              return month(\"short\", false);\n            case \"MMMM\":\n              // like January\n              return month(\"long\", false);\n            case \"MMMMM\":\n              // like J\n              return month(\"narrow\", false);\n            // years\n            case \"y\":\n              // like 2014\n              return useDateTimeFormatter ? string({\n                year: \"numeric\"\n              }, \"year\") : _this.num(dt.year);\n            case \"yy\":\n              // like 14\n              return useDateTimeFormatter ? string({\n                year: \"2-digit\"\n              }, \"year\") : _this.num(dt.year.toString().slice(-2), 2);\n            case \"yyyy\":\n              // like 0012\n              return useDateTimeFormatter ? string({\n                year: \"numeric\"\n              }, \"year\") : _this.num(dt.year, 4);\n            case \"yyyyyy\":\n              // like 000012\n              return useDateTimeFormatter ? string({\n                year: \"numeric\"\n              }, \"year\") : _this.num(dt.year, 6);\n            // eras\n            case \"G\":\n              // like AD\n              return era(\"short\");\n            case \"GG\":\n              // like Anno Domini\n              return era(\"long\");\n            case \"GGGGG\":\n              return era(\"narrow\");\n            case \"kk\":\n              return _this.num(dt.weekYear.toString().slice(-2), 2);\n            case \"kkkk\":\n              return _this.num(dt.weekYear, 4);\n            case \"W\":\n              return _this.num(dt.weekNumber);\n            case \"WW\":\n              return _this.num(dt.weekNumber, 2);\n            case \"n\":\n              return _this.num(dt.localWeekNumber);\n            case \"nn\":\n              return _this.num(dt.localWeekNumber, 2);\n            case \"ii\":\n              return _this.num(dt.localWeekYear.toString().slice(-2), 2);\n            case \"iiii\":\n              return _this.num(dt.localWeekYear, 4);\n            case \"o\":\n              return _this.num(dt.ordinal);\n            case \"ooo\":\n              return _this.num(dt.ordinal, 3);\n            case \"q\":\n              // like 1\n              return _this.num(dt.quarter);\n            case \"qq\":\n              // like 01\n              return _this.num(dt.quarter, 2);\n            case \"X\":\n              return _this.num(Math.floor(dt.ts / 1000));\n            case \"x\":\n              return _this.num(dt.ts);\n            default:\n              return maybeMacro(token);\n          }\n        };\n      return stringifyTokens(Formatter.parseFormat(fmt), tokenToString);\n    };\n    _proto.formatDurationFromString = function formatDurationFromString(dur, fmt) {\n      var _this2 = this;\n      var tokenToField = function tokenToField(token) {\n          switch (token[0]) {\n            case \"S\":\n              return \"millisecond\";\n            case \"s\":\n              return \"second\";\n            case \"m\":\n              return \"minute\";\n            case \"h\":\n              return \"hour\";\n            case \"d\":\n              return \"day\";\n            case \"w\":\n              return \"week\";\n            case \"M\":\n              return \"month\";\n            case \"y\":\n              return \"year\";\n            default:\n              return null;\n          }\n        },\n        tokenToString = function tokenToString(lildur) {\n          return function (token) {\n            var mapped = tokenToField(token);\n            if (mapped) {\n              return _this2.num(lildur.get(mapped), token.length);\n            } else {\n              return token;\n            }\n          };\n        },\n        tokens = Formatter.parseFormat(fmt),\n        realTokens = tokens.reduce(function (found, _ref) {\n          var literal = _ref.literal,\n            val = _ref.val;\n          return literal ? found : found.concat(val);\n        }, []),\n        collapsed = dur.shiftTo.apply(dur, realTokens.map(tokenToField).filter(function (t) {\n          return t;\n        }));\n      return stringifyTokens(tokens, tokenToString(collapsed));\n    };\n    return Formatter;\n  }();\n\n  /*\n   * This file handles parsing for well-specified formats. Here's how it works:\n   * Two things go into parsing: a regex to match with and an extractor to take apart the groups in the match.\n   * An extractor is just a function that takes a regex match array and returns a { year: ..., month: ... } object\n   * parse() does the work of executing the regex and applying the extractor. It takes multiple regex/extractor pairs to try in sequence.\n   * Extractors can take a \"cursor\" representing the offset in the match to look at. This makes it easy to combine extractors.\n   * combineExtractors() does the work of combining them, keeping track of the cursor through multiple extractions.\n   * Some extractions are super dumb and simpleParse and fromStrings help DRY them.\n   */\n\n  var ianaRegex = /[A-Za-z_+-]{1,256}(?::?\\/[A-Za-z0-9_+-]{1,256}(?:\\/[A-Za-z0-9_+-]{1,256})?)?/;\n  function combineRegexes() {\n    for (var _len = arguments.length, regexes = new Array(_len), _key = 0; _key < _len; _key++) {\n      regexes[_key] = arguments[_key];\n    }\n    var full = regexes.reduce(function (f, r) {\n      return f + r.source;\n    }, \"\");\n    return RegExp(\"^\" + full + \"$\");\n  }\n  function combineExtractors() {\n    for (var _len2 = arguments.length, extractors = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n      extractors[_key2] = arguments[_key2];\n    }\n    return function (m) {\n      return extractors.reduce(function (_ref, ex) {\n        var mergedVals = _ref[0],\n          mergedZone = _ref[1],\n          cursor = _ref[2];\n        var _ex = ex(m, cursor),\n          val = _ex[0],\n          zone = _ex[1],\n          next = _ex[2];\n        return [_extends({}, mergedVals, val), zone || mergedZone, next];\n      }, [{}, null, 1]).slice(0, 2);\n    };\n  }\n  function parse(s) {\n    if (s == null) {\n      return [null, null];\n    }\n    for (var _len3 = arguments.length, patterns = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n      patterns[_key3 - 1] = arguments[_key3];\n    }\n    for (var _i = 0, _patterns = patterns; _i < _patterns.length; _i++) {\n      var _patterns$_i = _patterns[_i],\n        regex = _patterns$_i[0],\n        extractor = _patterns$_i[1];\n      var m = regex.exec(s);\n      if (m) {\n        return extractor(m);\n      }\n    }\n    return [null, null];\n  }\n  function simpleParse() {\n    for (var _len4 = arguments.length, keys = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {\n      keys[_key4] = arguments[_key4];\n    }\n    return function (match, cursor) {\n      var ret = {};\n      var i;\n      for (i = 0; i < keys.length; i++) {\n        ret[keys[i]] = parseInteger(match[cursor + i]);\n      }\n      return [ret, null, cursor + i];\n    };\n  }\n\n  // ISO and SQL parsing\n  var offsetRegex = /(?:(Z)|([+-]\\d\\d)(?::?(\\d\\d))?)/;\n  var isoExtendedZone = \"(?:\" + offsetRegex.source + \"?(?:\\\\[(\" + ianaRegex.source + \")\\\\])?)?\";\n  var isoTimeBaseRegex = /(\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:[.,](\\d{1,30}))?)?)?/;\n  var isoTimeRegex = RegExp(\"\" + isoTimeBaseRegex.source + isoExtendedZone);\n  var isoTimeExtensionRegex = RegExp(\"(?:T\" + isoTimeRegex.source + \")?\");\n  var isoYmdRegex = /([+-]\\d{6}|\\d{4})(?:-?(\\d\\d)(?:-?(\\d\\d))?)?/;\n  var isoWeekRegex = /(\\d{4})-?W(\\d\\d)(?:-?(\\d))?/;\n  var isoOrdinalRegex = /(\\d{4})-?(\\d{3})/;\n  var extractISOWeekData = simpleParse(\"weekYear\", \"weekNumber\", \"weekDay\");\n  var extractISOOrdinalData = simpleParse(\"year\", \"ordinal\");\n  var sqlYmdRegex = /(\\d{4})-(\\d\\d)-(\\d\\d)/; // dumbed-down version of the ISO one\n  var sqlTimeRegex = RegExp(isoTimeBaseRegex.source + \" ?(?:\" + offsetRegex.source + \"|(\" + ianaRegex.source + \"))?\");\n  var sqlTimeExtensionRegex = RegExp(\"(?: \" + sqlTimeRegex.source + \")?\");\n  function int(match, pos, fallback) {\n    var m = match[pos];\n    return isUndefined(m) ? fallback : parseInteger(m);\n  }\n  function extractISOYmd(match, cursor) {\n    var item = {\n      year: int(match, cursor),\n      month: int(match, cursor + 1, 1),\n      day: int(match, cursor + 2, 1)\n    };\n    return [item, null, cursor + 3];\n  }\n  function extractISOTime(match, cursor) {\n    var item = {\n      hours: int(match, cursor, 0),\n      minutes: int(match, cursor + 1, 0),\n      seconds: int(match, cursor + 2, 0),\n      milliseconds: parseMillis(match[cursor + 3])\n    };\n    return [item, null, cursor + 4];\n  }\n  function extractISOOffset(match, cursor) {\n    var local = !match[cursor] && !match[cursor + 1],\n      fullOffset = signedOffset(match[cursor + 1], match[cursor + 2]),\n      zone = local ? null : FixedOffsetZone.instance(fullOffset);\n    return [{}, zone, cursor + 3];\n  }\n  function extractIANAZone(match, cursor) {\n    var zone = match[cursor] ? IANAZone.create(match[cursor]) : null;\n    return [{}, zone, cursor + 1];\n  }\n\n  // ISO time parsing\n\n  var isoTimeOnly = RegExp(\"^T?\" + isoTimeBaseRegex.source + \"$\");\n\n  // ISO duration parsing\n\n  var isoDuration = /^-?P(?:(?:(-?\\d{1,20}(?:\\.\\d{1,20})?)Y)?(?:(-?\\d{1,20}(?:\\.\\d{1,20})?)M)?(?:(-?\\d{1,20}(?:\\.\\d{1,20})?)W)?(?:(-?\\d{1,20}(?:\\.\\d{1,20})?)D)?(?:T(?:(-?\\d{1,20}(?:\\.\\d{1,20})?)H)?(?:(-?\\d{1,20}(?:\\.\\d{1,20})?)M)?(?:(-?\\d{1,20})(?:[.,](-?\\d{1,20}))?S)?)?)$/;\n  function extractISODuration(match) {\n    var s = match[0],\n      yearStr = match[1],\n      monthStr = match[2],\n      weekStr = match[3],\n      dayStr = match[4],\n      hourStr = match[5],\n      minuteStr = match[6],\n      secondStr = match[7],\n      millisecondsStr = match[8];\n    var hasNegativePrefix = s[0] === \"-\";\n    var negativeSeconds = secondStr && secondStr[0] === \"-\";\n    var maybeNegate = function maybeNegate(num, force) {\n      if (force === void 0) {\n        force = false;\n      }\n      return num !== undefined && (force || num && hasNegativePrefix) ? -num : num;\n    };\n    return [{\n      years: maybeNegate(parseFloating(yearStr)),\n      months: maybeNegate(parseFloating(monthStr)),\n      weeks: maybeNegate(parseFloating(weekStr)),\n      days: maybeNegate(parseFloating(dayStr)),\n      hours: maybeNegate(parseFloating(hourStr)),\n      minutes: maybeNegate(parseFloating(minuteStr)),\n      seconds: maybeNegate(parseFloating(secondStr), secondStr === \"-0\"),\n      milliseconds: maybeNegate(parseMillis(millisecondsStr), negativeSeconds)\n    }];\n  }\n\n  // These are a little braindead. EDT *should* tell us that we're in, say, America/New_York\n  // and not just that we're in -240 *right now*. But since I don't think these are used that often\n  // I'm just going to ignore that\n  var obsOffsets = {\n    GMT: 0,\n    EDT: -4 * 60,\n    EST: -5 * 60,\n    CDT: -5 * 60,\n    CST: -6 * 60,\n    MDT: -6 * 60,\n    MST: -7 * 60,\n    PDT: -7 * 60,\n    PST: -8 * 60\n  };\n  function fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {\n    var result = {\n      year: yearStr.length === 2 ? untruncateYear(parseInteger(yearStr)) : parseInteger(yearStr),\n      month: monthsShort.indexOf(monthStr) + 1,\n      day: parseInteger(dayStr),\n      hour: parseInteger(hourStr),\n      minute: parseInteger(minuteStr)\n    };\n    if (secondStr) result.second = parseInteger(secondStr);\n    if (weekdayStr) {\n      result.weekday = weekdayStr.length > 3 ? weekdaysLong.indexOf(weekdayStr) + 1 : weekdaysShort.indexOf(weekdayStr) + 1;\n    }\n    return result;\n  }\n\n  // RFC 2822/5322\n  var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\\d\\d)(\\d\\d)))$/;\n  function extractRFC2822(match) {\n    var weekdayStr = match[1],\n      dayStr = match[2],\n      monthStr = match[3],\n      yearStr = match[4],\n      hourStr = match[5],\n      minuteStr = match[6],\n      secondStr = match[7],\n      obsOffset = match[8],\n      milOffset = match[9],\n      offHourStr = match[10],\n      offMinuteStr = match[11],\n      result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);\n    var offset;\n    if (obsOffset) {\n      offset = obsOffsets[obsOffset];\n    } else if (milOffset) {\n      offset = 0;\n    } else {\n      offset = signedOffset(offHourStr, offMinuteStr);\n    }\n    return [result, new FixedOffsetZone(offset)];\n  }\n  function preprocessRFC2822(s) {\n    // Remove comments and folding whitespace and replace multiple-spaces with a single space\n    return s.replace(/\\([^()]*\\)|[\\n\\t]/g, \" \").replace(/(\\s\\s+)/g, \" \").trim();\n  }\n\n  // http date\n\n  var rfc1123 = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\\d\\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\\d{4}) (\\d\\d):(\\d\\d):(\\d\\d) GMT$/,\n    rfc850 = /^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d\\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d) GMT$/,\n    ascii = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \\d|\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d) (\\d{4})$/;\n  function extractRFC1123Or850(match) {\n    var weekdayStr = match[1],\n      dayStr = match[2],\n      monthStr = match[3],\n      yearStr = match[4],\n      hourStr = match[5],\n      minuteStr = match[6],\n      secondStr = match[7],\n      result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);\n    return [result, FixedOffsetZone.utcInstance];\n  }\n  function extractASCII(match) {\n    var weekdayStr = match[1],\n      monthStr = match[2],\n      dayStr = match[3],\n      hourStr = match[4],\n      minuteStr = match[5],\n      secondStr = match[6],\n      yearStr = match[7],\n      result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);\n    return [result, FixedOffsetZone.utcInstance];\n  }\n  var isoYmdWithTimeExtensionRegex = combineRegexes(isoYmdRegex, isoTimeExtensionRegex);\n  var isoWeekWithTimeExtensionRegex = combineRegexes(isoWeekRegex, isoTimeExtensionRegex);\n  var isoOrdinalWithTimeExtensionRegex = combineRegexes(isoOrdinalRegex, isoTimeExtensionRegex);\n  var isoTimeCombinedRegex = combineRegexes(isoTimeRegex);\n  var extractISOYmdTimeAndOffset = combineExtractors(extractISOYmd, extractISOTime, extractISOOffset, extractIANAZone);\n  var extractISOWeekTimeAndOffset = combineExtractors(extractISOWeekData, extractISOTime, extractISOOffset, extractIANAZone);\n  var extractISOOrdinalDateAndTime = combineExtractors(extractISOOrdinalData, extractISOTime, extractISOOffset, extractIANAZone);\n  var extractISOTimeAndOffset = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);\n\n  /*\n   * @private\n   */\n\n  function parseISODate(s) {\n    return parse(s, [isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset], [isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDateAndTime], [isoTimeCombinedRegex, extractISOTimeAndOffset]);\n  }\n  function parseRFC2822Date(s) {\n    return parse(preprocessRFC2822(s), [rfc2822, extractRFC2822]);\n  }\n  function parseHTTPDate(s) {\n    return parse(s, [rfc1123, extractRFC1123Or850], [rfc850, extractRFC1123Or850], [ascii, extractASCII]);\n  }\n  function parseISODuration(s) {\n    return parse(s, [isoDuration, extractISODuration]);\n  }\n  var extractISOTimeOnly = combineExtractors(extractISOTime);\n  function parseISOTimeOnly(s) {\n    return parse(s, [isoTimeOnly, extractISOTimeOnly]);\n  }\n  var sqlYmdWithTimeExtensionRegex = combineRegexes(sqlYmdRegex, sqlTimeExtensionRegex);\n  var sqlTimeCombinedRegex = combineRegexes(sqlTimeRegex);\n  var extractISOTimeOffsetAndIANAZone = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);\n  function parseSQL(s) {\n    return parse(s, [sqlYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone]);\n  }\n\n  var INVALID$2 = \"Invalid Duration\";\n\n  // unit conversion constants\n  var lowOrderMatrix = {\n      weeks: {\n        days: 7,\n        hours: 7 * 24,\n        minutes: 7 * 24 * 60,\n        seconds: 7 * 24 * 60 * 60,\n        milliseconds: 7 * 24 * 60 * 60 * 1000\n      },\n      days: {\n        hours: 24,\n        minutes: 24 * 60,\n        seconds: 24 * 60 * 60,\n        milliseconds: 24 * 60 * 60 * 1000\n      },\n      hours: {\n        minutes: 60,\n        seconds: 60 * 60,\n        milliseconds: 60 * 60 * 1000\n      },\n      minutes: {\n        seconds: 60,\n        milliseconds: 60 * 1000\n      },\n      seconds: {\n        milliseconds: 1000\n      }\n    },\n    casualMatrix = _extends({\n      years: {\n        quarters: 4,\n        months: 12,\n        weeks: 52,\n        days: 365,\n        hours: 365 * 24,\n        minutes: 365 * 24 * 60,\n        seconds: 365 * 24 * 60 * 60,\n        milliseconds: 365 * 24 * 60 * 60 * 1000\n      },\n      quarters: {\n        months: 3,\n        weeks: 13,\n        days: 91,\n        hours: 91 * 24,\n        minutes: 91 * 24 * 60,\n        seconds: 91 * 24 * 60 * 60,\n        milliseconds: 91 * 24 * 60 * 60 * 1000\n      },\n      months: {\n        weeks: 4,\n        days: 30,\n        hours: 30 * 24,\n        minutes: 30 * 24 * 60,\n        seconds: 30 * 24 * 60 * 60,\n        milliseconds: 30 * 24 * 60 * 60 * 1000\n      }\n    }, lowOrderMatrix),\n    daysInYearAccurate = 146097.0 / 400,\n    daysInMonthAccurate = 146097.0 / 4800,\n    accurateMatrix = _extends({\n      years: {\n        quarters: 4,\n        months: 12,\n        weeks: daysInYearAccurate / 7,\n        days: daysInYearAccurate,\n        hours: daysInYearAccurate * 24,\n        minutes: daysInYearAccurate * 24 * 60,\n        seconds: daysInYearAccurate * 24 * 60 * 60,\n        milliseconds: daysInYearAccurate * 24 * 60 * 60 * 1000\n      },\n      quarters: {\n        months: 3,\n        weeks: daysInYearAccurate / 28,\n        days: daysInYearAccurate / 4,\n        hours: daysInYearAccurate * 24 / 4,\n        minutes: daysInYearAccurate * 24 * 60 / 4,\n        seconds: daysInYearAccurate * 24 * 60 * 60 / 4,\n        milliseconds: daysInYearAccurate * 24 * 60 * 60 * 1000 / 4\n      },\n      months: {\n        weeks: daysInMonthAccurate / 7,\n        days: daysInMonthAccurate,\n        hours: daysInMonthAccurate * 24,\n        minutes: daysInMonthAccurate * 24 * 60,\n        seconds: daysInMonthAccurate * 24 * 60 * 60,\n        milliseconds: daysInMonthAccurate * 24 * 60 * 60 * 1000\n      }\n    }, lowOrderMatrix);\n\n  // units ordered by size\n  var orderedUnits$1 = [\"years\", \"quarters\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", \"seconds\", \"milliseconds\"];\n  var reverseUnits = orderedUnits$1.slice(0).reverse();\n\n  // clone really means \"create another instance just like this one, but with these changes\"\n  function clone$1(dur, alts, clear) {\n    if (clear === void 0) {\n      clear = false;\n    }\n    // deep merge for vals\n    var conf = {\n      values: clear ? alts.values : _extends({}, dur.values, alts.values || {}),\n      loc: dur.loc.clone(alts.loc),\n      conversionAccuracy: alts.conversionAccuracy || dur.conversionAccuracy,\n      matrix: alts.matrix || dur.matrix\n    };\n    return new Duration(conf);\n  }\n  function durationToMillis(matrix, vals) {\n    var _vals$milliseconds;\n    var sum = (_vals$milliseconds = vals.milliseconds) != null ? _vals$milliseconds : 0;\n    for (var _iterator = _createForOfIteratorHelperLoose(reverseUnits.slice(1)), _step; !(_step = _iterator()).done;) {\n      var unit = _step.value;\n      if (vals[unit]) {\n        sum += vals[unit] * matrix[unit][\"milliseconds\"];\n      }\n    }\n    return sum;\n  }\n\n  // NB: mutates parameters\n  function normalizeValues(matrix, vals) {\n    // the logic below assumes the overall value of the duration is positive\n    // if this is not the case, factor is used to make it so\n    var factor = durationToMillis(matrix, vals) < 0 ? -1 : 1;\n    orderedUnits$1.reduceRight(function (previous, current) {\n      if (!isUndefined(vals[current])) {\n        if (previous) {\n          var previousVal = vals[previous] * factor;\n          var conv = matrix[current][previous];\n\n          // if (previousVal < 0):\n          // lower order unit is negative (e.g. { years: 2, days: -2 })\n          // normalize this by reducing the higher order unit by the appropriate amount\n          // and increasing the lower order unit\n          // this can never make the higher order unit negative, because this function only operates\n          // on positive durations, so the amount of time represented by the lower order unit cannot\n          // be larger than the higher order unit\n          // else:\n          // lower order unit is positive (e.g. { years: 2, days: 450 } or { years: -2, days: 450 })\n          // in this case we attempt to convert as much as possible from the lower order unit into\n          // the higher order one\n          //\n          // Math.floor takes care of both of these cases, rounding away from 0\n          // if previousVal < 0 it makes the absolute value larger\n          // if previousVal >= it makes the absolute value smaller\n          var rollUp = Math.floor(previousVal / conv);\n          vals[current] += rollUp * factor;\n          vals[previous] -= rollUp * conv * factor;\n        }\n        return current;\n      } else {\n        return previous;\n      }\n    }, null);\n\n    // try to convert any decimals into smaller units if possible\n    // for example for { years: 2.5, days: 0, seconds: 0 } we want to get { years: 2, days: 182, hours: 12 }\n    orderedUnits$1.reduce(function (previous, current) {\n      if (!isUndefined(vals[current])) {\n        if (previous) {\n          var fraction = vals[previous] % 1;\n          vals[previous] -= fraction;\n          vals[current] += fraction * matrix[previous][current];\n        }\n        return current;\n      } else {\n        return previous;\n      }\n    }, null);\n  }\n\n  // Remove all properties with a value of 0 from an object\n  function removeZeroes(vals) {\n    var newVals = {};\n    for (var _i = 0, _Object$entries = Object.entries(vals); _i < _Object$entries.length; _i++) {\n      var _Object$entries$_i = _Object$entries[_i],\n        key = _Object$entries$_i[0],\n        value = _Object$entries$_i[1];\n      if (value !== 0) {\n        newVals[key] = value;\n      }\n    }\n    return newVals;\n  }\n\n  /**\n   * A Duration object represents a period of time, like \"2 months\" or \"1 day, 1 hour\". Conceptually, it's just a map of units to their quantities, accompanied by some additional configuration and methods for creating, parsing, interrogating, transforming, and formatting them. They can be used on their own or in conjunction with other Luxon types; for example, you can use {@link DateTime#plus} to add a Duration object to a DateTime, producing another DateTime.\n   *\n   * Here is a brief overview of commonly used methods and getters in Duration:\n   *\n   * * **Creation** To create a Duration, use {@link Duration.fromMillis}, {@link Duration.fromObject}, or {@link Duration.fromISO}.\n   * * **Unit values** See the {@link Duration#years}, {@link Duration#months}, {@link Duration#weeks}, {@link Duration#days}, {@link Duration#hours}, {@link Duration#minutes}, {@link Duration#seconds}, {@link Duration#milliseconds} accessors.\n   * * **Configuration** See  {@link Duration#locale} and {@link Duration#numberingSystem} accessors.\n   * * **Transformation** To create new Durations out of old ones use {@link Duration#plus}, {@link Duration#minus}, {@link Duration#normalize}, {@link Duration#set}, {@link Duration#reconfigure}, {@link Duration#shiftTo}, and {@link Duration#negate}.\n   * * **Output** To convert the Duration into other representations, see {@link Duration#as}, {@link Duration#toISO}, {@link Duration#toFormat}, and {@link Duration#toJSON}\n   *\n   * There's are more methods documented below. In addition, for more information on subtler topics like internationalization and validity, see the external documentation.\n   */\n  var Duration = /*#__PURE__*/function (_Symbol$for) {\n    /**\n     * @private\n     */\n    function Duration(config) {\n      var accurate = config.conversionAccuracy === \"longterm\" || false;\n      var matrix = accurate ? accurateMatrix : casualMatrix;\n      if (config.matrix) {\n        matrix = config.matrix;\n      }\n\n      /**\n       * @access private\n       */\n      this.values = config.values;\n      /**\n       * @access private\n       */\n      this.loc = config.loc || Locale.create();\n      /**\n       * @access private\n       */\n      this.conversionAccuracy = accurate ? \"longterm\" : \"casual\";\n      /**\n       * @access private\n       */\n      this.invalid = config.invalid || null;\n      /**\n       * @access private\n       */\n      this.matrix = matrix;\n      /**\n       * @access private\n       */\n      this.isLuxonDuration = true;\n    }\n\n    /**\n     * Create Duration from a number of milliseconds.\n     * @param {number} count of milliseconds\n     * @param {Object} opts - options for parsing\n     * @param {string} [opts.locale='en-US'] - the locale to use\n     * @param {string} opts.numberingSystem - the numbering system to use\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @return {Duration}\n     */\n    Duration.fromMillis = function fromMillis(count, opts) {\n      return Duration.fromObject({\n        milliseconds: count\n      }, opts);\n    }\n\n    /**\n     * Create a Duration from a JavaScript object with keys like 'years' and 'hours'.\n     * If this object is empty then a zero milliseconds duration is returned.\n     * @param {Object} obj - the object to create the DateTime from\n     * @param {number} obj.years\n     * @param {number} obj.quarters\n     * @param {number} obj.months\n     * @param {number} obj.weeks\n     * @param {number} obj.days\n     * @param {number} obj.hours\n     * @param {number} obj.minutes\n     * @param {number} obj.seconds\n     * @param {number} obj.milliseconds\n     * @param {Object} [opts=[]] - options for creating this Duration\n     * @param {string} [opts.locale='en-US'] - the locale to use\n     * @param {string} opts.numberingSystem - the numbering system to use\n     * @param {string} [opts.conversionAccuracy='casual'] - the preset conversion system to use\n     * @param {string} [opts.matrix=Object] - the custom conversion system to use\n     * @return {Duration}\n     */;\n    Duration.fromObject = function fromObject(obj, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (obj == null || typeof obj !== \"object\") {\n        throw new InvalidArgumentError(\"Duration.fromObject: argument expected to be an object, got \" + (obj === null ? \"null\" : typeof obj));\n      }\n      return new Duration({\n        values: normalizeObject(obj, Duration.normalizeUnit),\n        loc: Locale.fromObject(opts),\n        conversionAccuracy: opts.conversionAccuracy,\n        matrix: opts.matrix\n      });\n    }\n\n    /**\n     * Create a Duration from DurationLike.\n     *\n     * @param {Object | number | Duration} durationLike\n     * One of:\n     * - object with keys like 'years' and 'hours'.\n     * - number representing milliseconds\n     * - Duration instance\n     * @return {Duration}\n     */;\n    Duration.fromDurationLike = function fromDurationLike(durationLike) {\n      if (isNumber(durationLike)) {\n        return Duration.fromMillis(durationLike);\n      } else if (Duration.isDuration(durationLike)) {\n        return durationLike;\n      } else if (typeof durationLike === \"object\") {\n        return Duration.fromObject(durationLike);\n      } else {\n        throw new InvalidArgumentError(\"Unknown duration argument \" + durationLike + \" of type \" + typeof durationLike);\n      }\n    }\n\n    /**\n     * Create a Duration from an ISO 8601 duration string.\n     * @param {string} text - text to parse\n     * @param {Object} opts - options for parsing\n     * @param {string} [opts.locale='en-US'] - the locale to use\n     * @param {string} opts.numberingSystem - the numbering system to use\n     * @param {string} [opts.conversionAccuracy='casual'] - the preset conversion system to use\n     * @param {string} [opts.matrix=Object] - the preset conversion system to use\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Durations\n     * @example Duration.fromISO('P3Y6M1W4DT12H30M5S').toObject() //=> { years: 3, months: 6, weeks: 1, days: 4, hours: 12, minutes: 30, seconds: 5 }\n     * @example Duration.fromISO('PT23H').toObject() //=> { hours: 23 }\n     * @example Duration.fromISO('P5Y3M').toObject() //=> { years: 5, months: 3 }\n     * @return {Duration}\n     */;\n    Duration.fromISO = function fromISO(text, opts) {\n      var _parseISODuration = parseISODuration(text),\n        parsed = _parseISODuration[0];\n      if (parsed) {\n        return Duration.fromObject(parsed, opts);\n      } else {\n        return Duration.invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as ISO 8601\");\n      }\n    }\n\n    /**\n     * Create a Duration from an ISO 8601 time string.\n     * @param {string} text - text to parse\n     * @param {Object} opts - options for parsing\n     * @param {string} [opts.locale='en-US'] - the locale to use\n     * @param {string} opts.numberingSystem - the numbering system to use\n     * @param {string} [opts.conversionAccuracy='casual'] - the preset conversion system to use\n     * @param {string} [opts.matrix=Object] - the conversion system to use\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Times\n     * @example Duration.fromISOTime('11:22:33.444').toObject() //=> { hours: 11, minutes: 22, seconds: 33, milliseconds: 444 }\n     * @example Duration.fromISOTime('11:00').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }\n     * @example Duration.fromISOTime('T11:00').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }\n     * @example Duration.fromISOTime('1100').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }\n     * @example Duration.fromISOTime('T1100').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }\n     * @return {Duration}\n     */;\n    Duration.fromISOTime = function fromISOTime(text, opts) {\n      var _parseISOTimeOnly = parseISOTimeOnly(text),\n        parsed = _parseISOTimeOnly[0];\n      if (parsed) {\n        return Duration.fromObject(parsed, opts);\n      } else {\n        return Duration.invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as ISO 8601\");\n      }\n    }\n\n    /**\n     * Create an invalid Duration.\n     * @param {string} reason - simple string of why this datetime is invalid. Should not contain parameters or anything else data-dependent\n     * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information\n     * @return {Duration}\n     */;\n    Duration.invalid = function invalid(reason, explanation) {\n      if (explanation === void 0) {\n        explanation = null;\n      }\n      if (!reason) {\n        throw new InvalidArgumentError(\"need to specify a reason the Duration is invalid\");\n      }\n      var invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);\n      if (Settings.throwOnInvalid) {\n        throw new InvalidDurationError(invalid);\n      } else {\n        return new Duration({\n          invalid: invalid\n        });\n      }\n    }\n\n    /**\n     * @private\n     */;\n    Duration.normalizeUnit = function normalizeUnit(unit) {\n      var normalized = {\n        year: \"years\",\n        years: \"years\",\n        quarter: \"quarters\",\n        quarters: \"quarters\",\n        month: \"months\",\n        months: \"months\",\n        week: \"weeks\",\n        weeks: \"weeks\",\n        day: \"days\",\n        days: \"days\",\n        hour: \"hours\",\n        hours: \"hours\",\n        minute: \"minutes\",\n        minutes: \"minutes\",\n        second: \"seconds\",\n        seconds: \"seconds\",\n        millisecond: \"milliseconds\",\n        milliseconds: \"milliseconds\"\n      }[unit ? unit.toLowerCase() : unit];\n      if (!normalized) throw new InvalidUnitError(unit);\n      return normalized;\n    }\n\n    /**\n     * Check if an object is a Duration. Works across context boundaries\n     * @param {object} o\n     * @return {boolean}\n     */;\n    Duration.isDuration = function isDuration(o) {\n      return o && o.isLuxonDuration || false;\n    }\n\n    /**\n     * Get  the locale of a Duration, such 'en-GB'\n     * @type {string}\n     */;\n    var _proto = Duration.prototype;\n    /**\n     * Returns a string representation of this Duration formatted according to the specified format string. You may use these tokens:\n     * * `S` for milliseconds\n     * * `s` for seconds\n     * * `m` for minutes\n     * * `h` for hours\n     * * `d` for days\n     * * `w` for weeks\n     * * `M` for months\n     * * `y` for years\n     * Notes:\n     * * Add padding by repeating the token, e.g. \"yy\" pads the years to two digits, \"hhhh\" pads the hours out to four digits\n     * * Tokens can be escaped by wrapping with single quotes.\n     * * The duration will be converted to the set of units in the format string using {@link Duration#shiftTo} and the Durations's conversion accuracy setting.\n     * @param {string} fmt - the format string\n     * @param {Object} opts - options\n     * @param {boolean} [opts.floor=true] - floor numerical values\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat(\"y d s\") //=> \"1 6 2\"\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat(\"yy dd sss\") //=> \"01 06 002\"\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat(\"M S\") //=> \"12 518402000\"\n     * @return {string}\n     */\n    _proto.toFormat = function toFormat(fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      // reverse-compat since 1.2; we always round down now, never up, and we do it by default\n      var fmtOpts = _extends({}, opts, {\n        floor: opts.round !== false && opts.floor !== false\n      });\n      return this.isValid ? Formatter.create(this.loc, fmtOpts).formatDurationFromString(this, fmt) : INVALID$2;\n    }\n\n    /**\n     * Returns a string representation of a Duration with all units included.\n     * To modify its behavior, use `listStyle` and any Intl.NumberFormat option, though `unitDisplay` is especially relevant.\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options\n     * @param {Object} opts - Formatting options. Accepts the same keys as the options parameter of the native `Intl.NumberFormat` constructor, as well as `listStyle`.\n     * @param {string} [opts.listStyle='narrow'] - How to format the merged list. Corresponds to the `style` property of the options parameter of the native `Intl.ListFormat` constructor.\n     * @example\n     * ```js\n     * var dur = Duration.fromObject({ days: 1, hours: 5, minutes: 6 })\n     * dur.toHuman() //=> '1 day, 5 hours, 6 minutes'\n     * dur.toHuman({ listStyle: \"long\" }) //=> '1 day, 5 hours, and 6 minutes'\n     * dur.toHuman({ unitDisplay: \"short\" }) //=> '1 day, 5 hr, 6 min'\n     * ```\n     */;\n    _proto.toHuman = function toHuman(opts) {\n      var _this = this;\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (!this.isValid) return INVALID$2;\n      var l = orderedUnits$1.map(function (unit) {\n        var val = _this.values[unit];\n        if (isUndefined(val)) {\n          return null;\n        }\n        return _this.loc.numberFormatter(_extends({\n          style: \"unit\",\n          unitDisplay: \"long\"\n        }, opts, {\n          unit: unit.slice(0, -1)\n        })).format(val);\n      }).filter(function (n) {\n        return n;\n      });\n      return this.loc.listFormatter(_extends({\n        type: \"conjunction\",\n        style: opts.listStyle || \"narrow\"\n      }, opts)).format(l);\n    }\n\n    /**\n     * Returns a JavaScript object with this Duration's values.\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toObject() //=> { years: 1, days: 6, seconds: 2 }\n     * @return {Object}\n     */;\n    _proto.toObject = function toObject() {\n      if (!this.isValid) return {};\n      return _extends({}, this.values);\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this Duration.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Durations\n     * @example Duration.fromObject({ years: 3, seconds: 45 }).toISO() //=> 'P3YT45S'\n     * @example Duration.fromObject({ months: 4, seconds: 45 }).toISO() //=> 'P4MT45S'\n     * @example Duration.fromObject({ months: 5 }).toISO() //=> 'P5M'\n     * @example Duration.fromObject({ minutes: 5 }).toISO() //=> 'PT5M'\n     * @example Duration.fromObject({ milliseconds: 6 }).toISO() //=> 'PT0.006S'\n     * @return {string}\n     */;\n    _proto.toISO = function toISO() {\n      // we could use the formatter, but this is an easier way to get the minimum string\n      if (!this.isValid) return null;\n      var s = \"P\";\n      if (this.years !== 0) s += this.years + \"Y\";\n      if (this.months !== 0 || this.quarters !== 0) s += this.months + this.quarters * 3 + \"M\";\n      if (this.weeks !== 0) s += this.weeks + \"W\";\n      if (this.days !== 0) s += this.days + \"D\";\n      if (this.hours !== 0 || this.minutes !== 0 || this.seconds !== 0 || this.milliseconds !== 0) s += \"T\";\n      if (this.hours !== 0) s += this.hours + \"H\";\n      if (this.minutes !== 0) s += this.minutes + \"M\";\n      if (this.seconds !== 0 || this.milliseconds !== 0)\n        // this will handle \"floating point madness\" by removing extra decimal places\n        // https://stackoverflow.com/questions/588004/is-floating-point-math-broken\n        s += roundTo(this.seconds + this.milliseconds / 1000, 3) + \"S\";\n      if (s === \"P\") s += \"T0S\";\n      return s;\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this Duration, formatted as a time of day.\n     * Note that this will return null if the duration is invalid, negative, or equal to or greater than 24 hours.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Times\n     * @param {Object} opts - options\n     * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0\n     * @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0\n     * @param {boolean} [opts.includePrefix=false] - include the `T` prefix\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example Duration.fromObject({ hours: 11 }).toISOTime() //=> '11:00:00.000'\n     * @example Duration.fromObject({ hours: 11 }).toISOTime({ suppressMilliseconds: true }) //=> '11:00:00'\n     * @example Duration.fromObject({ hours: 11 }).toISOTime({ suppressSeconds: true }) //=> '11:00'\n     * @example Duration.fromObject({ hours: 11 }).toISOTime({ includePrefix: true }) //=> 'T11:00:00.000'\n     * @example Duration.fromObject({ hours: 11 }).toISOTime({ format: 'basic' }) //=> '110000.000'\n     * @return {string}\n     */;\n    _proto.toISOTime = function toISOTime(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (!this.isValid) return null;\n      var millis = this.toMillis();\n      if (millis < 0 || millis >= 86400000) return null;\n      opts = _extends({\n        suppressMilliseconds: false,\n        suppressSeconds: false,\n        includePrefix: false,\n        format: \"extended\"\n      }, opts, {\n        includeOffset: false\n      });\n      var dateTime = DateTime.fromMillis(millis, {\n        zone: \"UTC\"\n      });\n      return dateTime.toISOTime(opts);\n    }\n\n    /**\n     * Returns an ISO 8601 representation of this Duration appropriate for use in JSON.\n     * @return {string}\n     */;\n    _proto.toJSON = function toJSON() {\n      return this.toISO();\n    }\n\n    /**\n     * Returns an ISO 8601 representation of this Duration appropriate for use in debugging.\n     * @return {string}\n     */;\n    _proto.toString = function toString() {\n      return this.toISO();\n    }\n\n    /**\n     * Returns a string representation of this Duration appropriate for the REPL.\n     * @return {string}\n     */;\n    _proto[_Symbol$for] = function () {\n      if (this.isValid) {\n        return \"Duration { values: \" + JSON.stringify(this.values) + \" }\";\n      } else {\n        return \"Duration { Invalid, reason: \" + this.invalidReason + \" }\";\n      }\n    }\n\n    /**\n     * Returns an milliseconds value of this Duration.\n     * @return {number}\n     */;\n    _proto.toMillis = function toMillis() {\n      if (!this.isValid) return NaN;\n      return durationToMillis(this.matrix, this.values);\n    }\n\n    /**\n     * Returns an milliseconds value of this Duration. Alias of {@link toMillis}\n     * @return {number}\n     */;\n    _proto.valueOf = function valueOf() {\n      return this.toMillis();\n    }\n\n    /**\n     * Make this Duration longer by the specified amount. Return a newly-constructed Duration.\n     * @param {Duration|Object|number} duration - The amount to add. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     * @return {Duration}\n     */;\n    _proto.plus = function plus(duration) {\n      if (!this.isValid) return this;\n      var dur = Duration.fromDurationLike(duration),\n        result = {};\n      for (var _i2 = 0, _orderedUnits = orderedUnits$1; _i2 < _orderedUnits.length; _i2++) {\n        var k = _orderedUnits[_i2];\n        if (hasOwnProperty(dur.values, k) || hasOwnProperty(this.values, k)) {\n          result[k] = dur.get(k) + this.get(k);\n        }\n      }\n      return clone$1(this, {\n        values: result\n      }, true);\n    }\n\n    /**\n     * Make this Duration shorter by the specified amount. Return a newly-constructed Duration.\n     * @param {Duration|Object|number} duration - The amount to subtract. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     * @return {Duration}\n     */;\n    _proto.minus = function minus(duration) {\n      if (!this.isValid) return this;\n      var dur = Duration.fromDurationLike(duration);\n      return this.plus(dur.negate());\n    }\n\n    /**\n     * Scale this Duration by the specified amount. Return a newly-constructed Duration.\n     * @param {function} fn - The function to apply to each unit. Arity is 1 or 2: the value of the unit and, optionally, the unit name. Must return a number.\n     * @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnits(x => x * 2) //=> { hours: 2, minutes: 60 }\n     * @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnits((x, u) => u === \"hours\" ? x * 2 : x) //=> { hours: 2, minutes: 30 }\n     * @return {Duration}\n     */;\n    _proto.mapUnits = function mapUnits(fn) {\n      if (!this.isValid) return this;\n      var result = {};\n      for (var _i3 = 0, _Object$keys = Object.keys(this.values); _i3 < _Object$keys.length; _i3++) {\n        var k = _Object$keys[_i3];\n        result[k] = asNumber(fn(this.values[k], k));\n      }\n      return clone$1(this, {\n        values: result\n      }, true);\n    }\n\n    /**\n     * Get the value of unit.\n     * @param {string} unit - a unit such as 'minute' or 'day'\n     * @example Duration.fromObject({years: 2, days: 3}).get('years') //=> 2\n     * @example Duration.fromObject({years: 2, days: 3}).get('months') //=> 0\n     * @example Duration.fromObject({years: 2, days: 3}).get('days') //=> 3\n     * @return {number}\n     */;\n    _proto.get = function get(unit) {\n      return this[Duration.normalizeUnit(unit)];\n    }\n\n    /**\n     * \"Set\" the values of specified units. Return a newly-constructed Duration.\n     * @param {Object} values - a mapping of units to numbers\n     * @example dur.set({ years: 2017 })\n     * @example dur.set({ hours: 8, minutes: 30 })\n     * @return {Duration}\n     */;\n    _proto.set = function set(values) {\n      if (!this.isValid) return this;\n      var mixed = _extends({}, this.values, normalizeObject(values, Duration.normalizeUnit));\n      return clone$1(this, {\n        values: mixed\n      });\n    }\n\n    /**\n     * \"Set\" the locale and/or numberingSystem.  Returns a newly-constructed Duration.\n     * @example dur.reconfigure({ locale: 'en-GB' })\n     * @return {Duration}\n     */;\n    _proto.reconfigure = function reconfigure(_temp) {\n      var _ref = _temp === void 0 ? {} : _temp,\n        locale = _ref.locale,\n        numberingSystem = _ref.numberingSystem,\n        conversionAccuracy = _ref.conversionAccuracy,\n        matrix = _ref.matrix;\n      var loc = this.loc.clone({\n        locale: locale,\n        numberingSystem: numberingSystem\n      });\n      var opts = {\n        loc: loc,\n        matrix: matrix,\n        conversionAccuracy: conversionAccuracy\n      };\n      return clone$1(this, opts);\n    }\n\n    /**\n     * Return the length of the duration in the specified unit.\n     * @param {string} unit - a unit such as 'minutes' or 'days'\n     * @example Duration.fromObject({years: 1}).as('days') //=> 365\n     * @example Duration.fromObject({years: 1}).as('months') //=> 12\n     * @example Duration.fromObject({hours: 60}).as('days') //=> 2.5\n     * @return {number}\n     */;\n    _proto.as = function as(unit) {\n      return this.isValid ? this.shiftTo(unit).get(unit) : NaN;\n    }\n\n    /**\n     * Reduce this Duration to its canonical representation in its current units.\n     * Assuming the overall value of the Duration is positive, this means:\n     * - excessive values for lower-order units are converted to higher-order units (if possible, see first and second example)\n     * - negative lower-order units are converted to higher order units (there must be such a higher order unit, otherwise\n     *   the overall value would be negative, see third example)\n     * - fractional values for higher-order units are converted to lower-order units (if possible, see fourth example)\n     *\n     * If the overall value is negative, the result of this method is equivalent to `this.negate().normalize().negate()`.\n     * @example Duration.fromObject({ years: 2, days: 5000 }).normalize().toObject() //=> { years: 15, days: 255 }\n     * @example Duration.fromObject({ days: 5000 }).normalize().toObject() //=> { days: 5000 }\n     * @example Duration.fromObject({ hours: 12, minutes: -45 }).normalize().toObject() //=> { hours: 11, minutes: 15 }\n     * @example Duration.fromObject({ years: 2.5, days: 0, hours: 0 }).normalize().toObject() //=> { years: 2, days: 182, hours: 12 }\n     * @return {Duration}\n     */;\n    _proto.normalize = function normalize() {\n      if (!this.isValid) return this;\n      var vals = this.toObject();\n      normalizeValues(this.matrix, vals);\n      return clone$1(this, {\n        values: vals\n      }, true);\n    }\n\n    /**\n     * Rescale units to its largest representation\n     * @example Duration.fromObject({ milliseconds: 90000 }).rescale().toObject() //=> { minutes: 1, seconds: 30 }\n     * @return {Duration}\n     */;\n    _proto.rescale = function rescale() {\n      if (!this.isValid) return this;\n      var vals = removeZeroes(this.normalize().shiftToAll().toObject());\n      return clone$1(this, {\n        values: vals\n      }, true);\n    }\n\n    /**\n     * Convert this Duration into its representation in a different set of units.\n     * @example Duration.fromObject({ hours: 1, seconds: 30 }).shiftTo('minutes', 'milliseconds').toObject() //=> { minutes: 60, milliseconds: 30000 }\n     * @return {Duration}\n     */;\n    _proto.shiftTo = function shiftTo() {\n      for (var _len = arguments.length, units = new Array(_len), _key = 0; _key < _len; _key++) {\n        units[_key] = arguments[_key];\n      }\n      if (!this.isValid) return this;\n      if (units.length === 0) {\n        return this;\n      }\n      units = units.map(function (u) {\n        return Duration.normalizeUnit(u);\n      });\n      var built = {},\n        accumulated = {},\n        vals = this.toObject();\n      var lastUnit;\n      for (var _i4 = 0, _orderedUnits2 = orderedUnits$1; _i4 < _orderedUnits2.length; _i4++) {\n        var k = _orderedUnits2[_i4];\n        if (units.indexOf(k) >= 0) {\n          lastUnit = k;\n          var own = 0;\n\n          // anything we haven't boiled down yet should get boiled to this unit\n          for (var ak in accumulated) {\n            own += this.matrix[ak][k] * accumulated[ak];\n            accumulated[ak] = 0;\n          }\n\n          // plus anything that's already in this unit\n          if (isNumber(vals[k])) {\n            own += vals[k];\n          }\n\n          // only keep the integer part for now in the hopes of putting any decimal part\n          // into a smaller unit later\n          var i = Math.trunc(own);\n          built[k] = i;\n          accumulated[k] = (own * 1000 - i * 1000) / 1000;\n\n          // otherwise, keep it in the wings to boil it later\n        } else if (isNumber(vals[k])) {\n          accumulated[k] = vals[k];\n        }\n      }\n\n      // anything leftover becomes the decimal for the last unit\n      // lastUnit must be defined since units is not empty\n      for (var key in accumulated) {\n        if (accumulated[key] !== 0) {\n          built[lastUnit] += key === lastUnit ? accumulated[key] : accumulated[key] / this.matrix[lastUnit][key];\n        }\n      }\n      normalizeValues(this.matrix, built);\n      return clone$1(this, {\n        values: built\n      }, true);\n    }\n\n    /**\n     * Shift this Duration to all available units.\n     * Same as shiftTo(\"years\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", \"seconds\", \"milliseconds\")\n     * @return {Duration}\n     */;\n    _proto.shiftToAll = function shiftToAll() {\n      if (!this.isValid) return this;\n      return this.shiftTo(\"years\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", \"seconds\", \"milliseconds\");\n    }\n\n    /**\n     * Return the negative of this Duration.\n     * @example Duration.fromObject({ hours: 1, seconds: 30 }).negate().toObject() //=> { hours: -1, seconds: -30 }\n     * @return {Duration}\n     */;\n    _proto.negate = function negate() {\n      if (!this.isValid) return this;\n      var negated = {};\n      for (var _i5 = 0, _Object$keys2 = Object.keys(this.values); _i5 < _Object$keys2.length; _i5++) {\n        var k = _Object$keys2[_i5];\n        negated[k] = this.values[k] === 0 ? 0 : -this.values[k];\n      }\n      return clone$1(this, {\n        values: negated\n      }, true);\n    }\n\n    /**\n     * Get the years.\n     * @type {number}\n     */;\n    /**\n     * Equality check\n     * Two Durations are equal iff they have the same units and the same values for each unit.\n     * @param {Duration} other\n     * @return {boolean}\n     */\n    _proto.equals = function equals(other) {\n      if (!this.isValid || !other.isValid) {\n        return false;\n      }\n      if (!this.loc.equals(other.loc)) {\n        return false;\n      }\n      function eq(v1, v2) {\n        // Consider 0 and undefined as equal\n        if (v1 === undefined || v1 === 0) return v2 === undefined || v2 === 0;\n        return v1 === v2;\n      }\n      for (var _i6 = 0, _orderedUnits3 = orderedUnits$1; _i6 < _orderedUnits3.length; _i6++) {\n        var u = _orderedUnits3[_i6];\n        if (!eq(this.values[u], other.values[u])) {\n          return false;\n        }\n      }\n      return true;\n    };\n    _createClass(Duration, [{\n      key: \"locale\",\n      get: function get() {\n        return this.isValid ? this.loc.locale : null;\n      }\n\n      /**\n       * Get the numbering system of a Duration, such 'beng'. The numbering system is used when formatting the Duration\n       *\n       * @type {string}\n       */\n    }, {\n      key: \"numberingSystem\",\n      get: function get() {\n        return this.isValid ? this.loc.numberingSystem : null;\n      }\n    }, {\n      key: \"years\",\n      get: function get() {\n        return this.isValid ? this.values.years || 0 : NaN;\n      }\n\n      /**\n       * Get the quarters.\n       * @type {number}\n       */\n    }, {\n      key: \"quarters\",\n      get: function get() {\n        return this.isValid ? this.values.quarters || 0 : NaN;\n      }\n\n      /**\n       * Get the months.\n       * @type {number}\n       */\n    }, {\n      key: \"months\",\n      get: function get() {\n        return this.isValid ? this.values.months || 0 : NaN;\n      }\n\n      /**\n       * Get the weeks\n       * @type {number}\n       */\n    }, {\n      key: \"weeks\",\n      get: function get() {\n        return this.isValid ? this.values.weeks || 0 : NaN;\n      }\n\n      /**\n       * Get the days.\n       * @type {number}\n       */\n    }, {\n      key: \"days\",\n      get: function get() {\n        return this.isValid ? this.values.days || 0 : NaN;\n      }\n\n      /**\n       * Get the hours.\n       * @type {number}\n       */\n    }, {\n      key: \"hours\",\n      get: function get() {\n        return this.isValid ? this.values.hours || 0 : NaN;\n      }\n\n      /**\n       * Get the minutes.\n       * @type {number}\n       */\n    }, {\n      key: \"minutes\",\n      get: function get() {\n        return this.isValid ? this.values.minutes || 0 : NaN;\n      }\n\n      /**\n       * Get the seconds.\n       * @return {number}\n       */\n    }, {\n      key: \"seconds\",\n      get: function get() {\n        return this.isValid ? this.values.seconds || 0 : NaN;\n      }\n\n      /**\n       * Get the milliseconds.\n       * @return {number}\n       */\n    }, {\n      key: \"milliseconds\",\n      get: function get() {\n        return this.isValid ? this.values.milliseconds || 0 : NaN;\n      }\n\n      /**\n       * Returns whether the Duration is invalid. Invalid durations are returned by diff operations\n       * on invalid DateTimes or Intervals.\n       * @return {boolean}\n       */\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return this.invalid === null;\n      }\n\n      /**\n       * Returns an error code if this Duration became invalid, or null if the Duration is valid\n       * @return {string}\n       */\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.invalid ? this.invalid.reason : null;\n      }\n\n      /**\n       * Returns an explanation of why this Duration became invalid, or null if the Duration is valid\n       * @type {string}\n       */\n    }, {\n      key: \"invalidExplanation\",\n      get: function get() {\n        return this.invalid ? this.invalid.explanation : null;\n      }\n    }]);\n    return Duration;\n  }(Symbol.for(\"nodejs.util.inspect.custom\"));\n\n  var INVALID$1 = \"Invalid Interval\";\n\n  // checks if the start is equal to or before the end\n  function validateStartEnd(start, end) {\n    if (!start || !start.isValid) {\n      return Interval.invalid(\"missing or invalid start\");\n    } else if (!end || !end.isValid) {\n      return Interval.invalid(\"missing or invalid end\");\n    } else if (end < start) {\n      return Interval.invalid(\"end before start\", \"The end of an interval must be after its start, but you had start=\" + start.toISO() + \" and end=\" + end.toISO());\n    } else {\n      return null;\n    }\n  }\n\n  /**\n   * An Interval object represents a half-open interval of time, where each endpoint is a {@link DateTime}. Conceptually, it's a container for those two endpoints, accompanied by methods for creating, parsing, interrogating, comparing, transforming, and formatting them.\n   *\n   * Here is a brief overview of the most commonly used methods and getters in Interval:\n   *\n   * * **Creation** To create an Interval, use {@link Interval.fromDateTimes}, {@link Interval.after}, {@link Interval.before}, or {@link Interval.fromISO}.\n   * * **Accessors** Use {@link Interval#start} and {@link Interval#end} to get the start and end.\n   * * **Interrogation** To analyze the Interval, use {@link Interval#count}, {@link Interval#length}, {@link Interval#hasSame}, {@link Interval#contains}, {@link Interval#isAfter}, or {@link Interval#isBefore}.\n   * * **Transformation** To create other Intervals out of this one, use {@link Interval#set}, {@link Interval#splitAt}, {@link Interval#splitBy}, {@link Interval#divideEqually}, {@link Interval.merge}, {@link Interval.xor}, {@link Interval#union}, {@link Interval#intersection}, or {@link Interval#difference}.\n   * * **Comparison** To compare this Interval to another one, use {@link Interval#equals}, {@link Interval#overlaps}, {@link Interval#abutsStart}, {@link Interval#abutsEnd}, {@link Interval#engulfs}\n   * * **Output** To convert the Interval into other representations, see {@link Interval#toString}, {@link Interval#toLocaleString}, {@link Interval#toISO}, {@link Interval#toISODate}, {@link Interval#toISOTime}, {@link Interval#toFormat}, and {@link Interval#toDuration}.\n   */\n  var Interval = /*#__PURE__*/function (_Symbol$for) {\n    /**\n     * @private\n     */\n    function Interval(config) {\n      /**\n       * @access private\n       */\n      this.s = config.start;\n      /**\n       * @access private\n       */\n      this.e = config.end;\n      /**\n       * @access private\n       */\n      this.invalid = config.invalid || null;\n      /**\n       * @access private\n       */\n      this.isLuxonInterval = true;\n    }\n\n    /**\n     * Create an invalid Interval.\n     * @param {string} reason - simple string of why this Interval is invalid. Should not contain parameters or anything else data-dependent\n     * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information\n     * @return {Interval}\n     */\n    Interval.invalid = function invalid(reason, explanation) {\n      if (explanation === void 0) {\n        explanation = null;\n      }\n      if (!reason) {\n        throw new InvalidArgumentError(\"need to specify a reason the Interval is invalid\");\n      }\n      var invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);\n      if (Settings.throwOnInvalid) {\n        throw new InvalidIntervalError(invalid);\n      } else {\n        return new Interval({\n          invalid: invalid\n        });\n      }\n    }\n\n    /**\n     * Create an Interval from a start DateTime and an end DateTime. Inclusive of the start but not the end.\n     * @param {DateTime|Date|Object} start\n     * @param {DateTime|Date|Object} end\n     * @return {Interval}\n     */;\n    Interval.fromDateTimes = function fromDateTimes(start, end) {\n      var builtStart = friendlyDateTime(start),\n        builtEnd = friendlyDateTime(end);\n      var validateError = validateStartEnd(builtStart, builtEnd);\n      if (validateError == null) {\n        return new Interval({\n          start: builtStart,\n          end: builtEnd\n        });\n      } else {\n        return validateError;\n      }\n    }\n\n    /**\n     * Create an Interval from a start DateTime and a Duration to extend to.\n     * @param {DateTime|Date|Object} start\n     * @param {Duration|Object|number} duration - the length of the Interval.\n     * @return {Interval}\n     */;\n    Interval.after = function after(start, duration) {\n      var dur = Duration.fromDurationLike(duration),\n        dt = friendlyDateTime(start);\n      return Interval.fromDateTimes(dt, dt.plus(dur));\n    }\n\n    /**\n     * Create an Interval from an end DateTime and a Duration to extend backwards to.\n     * @param {DateTime|Date|Object} end\n     * @param {Duration|Object|number} duration - the length of the Interval.\n     * @return {Interval}\n     */;\n    Interval.before = function before(end, duration) {\n      var dur = Duration.fromDurationLike(duration),\n        dt = friendlyDateTime(end);\n      return Interval.fromDateTimes(dt.minus(dur), dt);\n    }\n\n    /**\n     * Create an Interval from an ISO 8601 string.\n     * Accepts `<start>/<end>`, `<start>/<duration>`, and `<duration>/<end>` formats.\n     * @param {string} text - the ISO string to parse\n     * @param {Object} [opts] - options to pass {@link DateTime#fromISO} and optionally {@link Duration#fromISO}\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @return {Interval}\n     */;\n    Interval.fromISO = function fromISO(text, opts) {\n      var _split = (text || \"\").split(\"/\", 2),\n        s = _split[0],\n        e = _split[1];\n      if (s && e) {\n        var start, startIsValid;\n        try {\n          start = DateTime.fromISO(s, opts);\n          startIsValid = start.isValid;\n        } catch (e) {\n          startIsValid = false;\n        }\n        var end, endIsValid;\n        try {\n          end = DateTime.fromISO(e, opts);\n          endIsValid = end.isValid;\n        } catch (e) {\n          endIsValid = false;\n        }\n        if (startIsValid && endIsValid) {\n          return Interval.fromDateTimes(start, end);\n        }\n        if (startIsValid) {\n          var dur = Duration.fromISO(e, opts);\n          if (dur.isValid) {\n            return Interval.after(start, dur);\n          }\n        } else if (endIsValid) {\n          var _dur = Duration.fromISO(s, opts);\n          if (_dur.isValid) {\n            return Interval.before(end, _dur);\n          }\n        }\n      }\n      return Interval.invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as ISO 8601\");\n    }\n\n    /**\n     * Check if an object is an Interval. Works across context boundaries\n     * @param {object} o\n     * @return {boolean}\n     */;\n    Interval.isInterval = function isInterval(o) {\n      return o && o.isLuxonInterval || false;\n    }\n\n    /**\n     * Returns the start of the Interval\n     * @type {DateTime}\n     */;\n    var _proto = Interval.prototype;\n    /**\n     * Returns the length of the Interval in the specified unit.\n     * @param {string} unit - the unit (such as 'hours' or 'days') to return the length in.\n     * @return {number}\n     */\n    _proto.length = function length(unit) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n      return this.isValid ? this.toDuration.apply(this, [unit]).get(unit) : NaN;\n    }\n\n    /**\n     * Returns the count of minutes, hours, days, months, or years included in the Interval, even in part.\n     * Unlike {@link Interval#length} this counts sections of the calendar, not periods of time, e.g. specifying 'day'\n     * asks 'what dates are included in this interval?', not 'how many days long is this interval?'\n     * @param {string} [unit='milliseconds'] - the unit of time to count.\n     * @param {Object} opts - options\n     * @param {boolean} [opts.useLocaleWeeks=false] - If true, use weeks based on the locale, i.e. use the locale-dependent start of the week; this operation will always use the locale of the start DateTime\n     * @return {number}\n     */;\n    _proto.count = function count(unit, opts) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n      if (!this.isValid) return NaN;\n      var start = this.start.startOf(unit, opts);\n      var end;\n      if (opts != null && opts.useLocaleWeeks) {\n        end = this.end.reconfigure({\n          locale: start.locale\n        });\n      } else {\n        end = this.end;\n      }\n      end = end.startOf(unit, opts);\n      return Math.floor(end.diff(start, unit).get(unit)) + (end.valueOf() !== this.end.valueOf());\n    }\n\n    /**\n     * Returns whether this Interval's start and end are both in the same unit of time\n     * @param {string} unit - the unit of time to check sameness on\n     * @return {boolean}\n     */;\n    _proto.hasSame = function hasSame(unit) {\n      return this.isValid ? this.isEmpty() || this.e.minus(1).hasSame(this.s, unit) : false;\n    }\n\n    /**\n     * Return whether this Interval has the same start and end DateTimes.\n     * @return {boolean}\n     */;\n    _proto.isEmpty = function isEmpty() {\n      return this.s.valueOf() === this.e.valueOf();\n    }\n\n    /**\n     * Return whether this Interval's start is after the specified DateTime.\n     * @param {DateTime} dateTime\n     * @return {boolean}\n     */;\n    _proto.isAfter = function isAfter(dateTime) {\n      if (!this.isValid) return false;\n      return this.s > dateTime;\n    }\n\n    /**\n     * Return whether this Interval's end is before the specified DateTime.\n     * @param {DateTime} dateTime\n     * @return {boolean}\n     */;\n    _proto.isBefore = function isBefore(dateTime) {\n      if (!this.isValid) return false;\n      return this.e <= dateTime;\n    }\n\n    /**\n     * Return whether this Interval contains the specified DateTime.\n     * @param {DateTime} dateTime\n     * @return {boolean}\n     */;\n    _proto.contains = function contains(dateTime) {\n      if (!this.isValid) return false;\n      return this.s <= dateTime && this.e > dateTime;\n    }\n\n    /**\n     * \"Sets\" the start and/or end dates. Returns a newly-constructed Interval.\n     * @param {Object} values - the values to set\n     * @param {DateTime} values.start - the starting DateTime\n     * @param {DateTime} values.end - the ending DateTime\n     * @return {Interval}\n     */;\n    _proto.set = function set(_temp) {\n      var _ref = _temp === void 0 ? {} : _temp,\n        start = _ref.start,\n        end = _ref.end;\n      if (!this.isValid) return this;\n      return Interval.fromDateTimes(start || this.s, end || this.e);\n    }\n\n    /**\n     * Split this Interval at each of the specified DateTimes\n     * @param {...DateTime} dateTimes - the unit of time to count.\n     * @return {Array}\n     */;\n    _proto.splitAt = function splitAt() {\n      var _this = this;\n      if (!this.isValid) return [];\n      for (var _len = arguments.length, dateTimes = new Array(_len), _key = 0; _key < _len; _key++) {\n        dateTimes[_key] = arguments[_key];\n      }\n      var sorted = dateTimes.map(friendlyDateTime).filter(function (d) {\n          return _this.contains(d);\n        }).sort(function (a, b) {\n          return a.toMillis() - b.toMillis();\n        }),\n        results = [];\n      var s = this.s,\n        i = 0;\n      while (s < this.e) {\n        var added = sorted[i] || this.e,\n          next = +added > +this.e ? this.e : added;\n        results.push(Interval.fromDateTimes(s, next));\n        s = next;\n        i += 1;\n      }\n      return results;\n    }\n\n    /**\n     * Split this Interval into smaller Intervals, each of the specified length.\n     * Left over time is grouped into a smaller interval\n     * @param {Duration|Object|number} duration - The length of each resulting interval.\n     * @return {Array}\n     */;\n    _proto.splitBy = function splitBy(duration) {\n      var dur = Duration.fromDurationLike(duration);\n      if (!this.isValid || !dur.isValid || dur.as(\"milliseconds\") === 0) {\n        return [];\n      }\n      var s = this.s,\n        idx = 1,\n        next;\n      var results = [];\n      while (s < this.e) {\n        var added = this.start.plus(dur.mapUnits(function (x) {\n          return x * idx;\n        }));\n        next = +added > +this.e ? this.e : added;\n        results.push(Interval.fromDateTimes(s, next));\n        s = next;\n        idx += 1;\n      }\n      return results;\n    }\n\n    /**\n     * Split this Interval into the specified number of smaller intervals.\n     * @param {number} numberOfParts - The number of Intervals to divide the Interval into.\n     * @return {Array}\n     */;\n    _proto.divideEqually = function divideEqually(numberOfParts) {\n      if (!this.isValid) return [];\n      return this.splitBy(this.length() / numberOfParts).slice(0, numberOfParts);\n    }\n\n    /**\n     * Return whether this Interval overlaps with the specified Interval\n     * @param {Interval} other\n     * @return {boolean}\n     */;\n    _proto.overlaps = function overlaps(other) {\n      return this.e > other.s && this.s < other.e;\n    }\n\n    /**\n     * Return whether this Interval's end is adjacent to the specified Interval's start.\n     * @param {Interval} other\n     * @return {boolean}\n     */;\n    _proto.abutsStart = function abutsStart(other) {\n      if (!this.isValid) return false;\n      return +this.e === +other.s;\n    }\n\n    /**\n     * Return whether this Interval's start is adjacent to the specified Interval's end.\n     * @param {Interval} other\n     * @return {boolean}\n     */;\n    _proto.abutsEnd = function abutsEnd(other) {\n      if (!this.isValid) return false;\n      return +other.e === +this.s;\n    }\n\n    /**\n     * Returns true if this Interval fully contains the specified Interval, specifically if the intersect (of this Interval and the other Interval) is equal to the other Interval; false otherwise.\n     * @param {Interval} other\n     * @return {boolean}\n     */;\n    _proto.engulfs = function engulfs(other) {\n      if (!this.isValid) return false;\n      return this.s <= other.s && this.e >= other.e;\n    }\n\n    /**\n     * Return whether this Interval has the same start and end as the specified Interval.\n     * @param {Interval} other\n     * @return {boolean}\n     */;\n    _proto.equals = function equals(other) {\n      if (!this.isValid || !other.isValid) {\n        return false;\n      }\n      return this.s.equals(other.s) && this.e.equals(other.e);\n    }\n\n    /**\n     * Return an Interval representing the intersection of this Interval and the specified Interval.\n     * Specifically, the resulting Interval has the maximum start time and the minimum end time of the two Intervals.\n     * Returns null if the intersection is empty, meaning, the intervals don't intersect.\n     * @param {Interval} other\n     * @return {Interval}\n     */;\n    _proto.intersection = function intersection(other) {\n      if (!this.isValid) return this;\n      var s = this.s > other.s ? this.s : other.s,\n        e = this.e < other.e ? this.e : other.e;\n      if (s >= e) {\n        return null;\n      } else {\n        return Interval.fromDateTimes(s, e);\n      }\n    }\n\n    /**\n     * Return an Interval representing the union of this Interval and the specified Interval.\n     * Specifically, the resulting Interval has the minimum start time and the maximum end time of the two Intervals.\n     * @param {Interval} other\n     * @return {Interval}\n     */;\n    _proto.union = function union(other) {\n      if (!this.isValid) return this;\n      var s = this.s < other.s ? this.s : other.s,\n        e = this.e > other.e ? this.e : other.e;\n      return Interval.fromDateTimes(s, e);\n    }\n\n    /**\n     * Merge an array of Intervals into a equivalent minimal set of Intervals.\n     * Combines overlapping and adjacent Intervals.\n     * @param {Array} intervals\n     * @return {Array}\n     */;\n    Interval.merge = function merge(intervals) {\n      var _intervals$sort$reduc = intervals.sort(function (a, b) {\n          return a.s - b.s;\n        }).reduce(function (_ref2, item) {\n          var sofar = _ref2[0],\n            current = _ref2[1];\n          if (!current) {\n            return [sofar, item];\n          } else if (current.overlaps(item) || current.abutsStart(item)) {\n            return [sofar, current.union(item)];\n          } else {\n            return [sofar.concat([current]), item];\n          }\n        }, [[], null]),\n        found = _intervals$sort$reduc[0],\n        final = _intervals$sort$reduc[1];\n      if (final) {\n        found.push(final);\n      }\n      return found;\n    }\n\n    /**\n     * Return an array of Intervals representing the spans of time that only appear in one of the specified Intervals.\n     * @param {Array} intervals\n     * @return {Array}\n     */;\n    Interval.xor = function xor(intervals) {\n      var _Array$prototype;\n      var start = null,\n        currentCount = 0;\n      var results = [],\n        ends = intervals.map(function (i) {\n          return [{\n            time: i.s,\n            type: \"s\"\n          }, {\n            time: i.e,\n            type: \"e\"\n          }];\n        }),\n        flattened = (_Array$prototype = Array.prototype).concat.apply(_Array$prototype, ends),\n        arr = flattened.sort(function (a, b) {\n          return a.time - b.time;\n        });\n      for (var _iterator = _createForOfIteratorHelperLoose(arr), _step; !(_step = _iterator()).done;) {\n        var i = _step.value;\n        currentCount += i.type === \"s\" ? 1 : -1;\n        if (currentCount === 1) {\n          start = i.time;\n        } else {\n          if (start && +start !== +i.time) {\n            results.push(Interval.fromDateTimes(start, i.time));\n          }\n          start = null;\n        }\n      }\n      return Interval.merge(results);\n    }\n\n    /**\n     * Return an Interval representing the span of time in this Interval that doesn't overlap with any of the specified Intervals.\n     * @param {...Interval} intervals\n     * @return {Array}\n     */;\n    _proto.difference = function difference() {\n      var _this2 = this;\n      for (var _len2 = arguments.length, intervals = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n        intervals[_key2] = arguments[_key2];\n      }\n      return Interval.xor([this].concat(intervals)).map(function (i) {\n        return _this2.intersection(i);\n      }).filter(function (i) {\n        return i && !i.isEmpty();\n      });\n    }\n\n    /**\n     * Returns a string representation of this Interval appropriate for debugging.\n     * @return {string}\n     */;\n    _proto.toString = function toString() {\n      if (!this.isValid) return INVALID$1;\n      return \"[\" + this.s.toISO() + \" \\u2013 \" + this.e.toISO() + \")\";\n    }\n\n    /**\n     * Returns a string representation of this Interval appropriate for the REPL.\n     * @return {string}\n     */;\n    _proto[_Symbol$for] = function () {\n      if (this.isValid) {\n        return \"Interval { start: \" + this.s.toISO() + \", end: \" + this.e.toISO() + \" }\";\n      } else {\n        return \"Interval { Invalid, reason: \" + this.invalidReason + \" }\";\n      }\n    }\n\n    /**\n     * Returns a localized string representing this Interval. Accepts the same options as the\n     * Intl.DateTimeFormat constructor and any presets defined by Luxon, such as\n     * {@link DateTime.DATE_FULL} or {@link DateTime.TIME_SIMPLE}. The exact behavior of this method\n     * is browser-specific, but in general it will return an appropriate representation of the\n     * Interval in the assigned locale. Defaults to the system's locale if no locale has been\n     * specified.\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param {Object} [formatOpts=DateTime.DATE_SHORT] - Either a DateTime preset or\n     * Intl.DateTimeFormat constructor options.\n     * @param {Object} opts - Options to override the configuration of the start DateTime.\n     * @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(); //=> 11/7/2022 \u2013 11/8/2022\n     * @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL); //=> November 7 \u2013 8, 2022\n     * @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL, { locale: 'fr-FR' }); //=> 7\u20138 novembre 2022\n     * @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString(DateTime.TIME_SIMPLE); //=> 6:00 \u2013 8:00 PM\n     * @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> Mon, Nov 07, 6:00 \u2013 8:00 p\n     * @return {string}\n     */;\n    _proto.toLocaleString = function toLocaleString(formatOpts, opts) {\n      if (formatOpts === void 0) {\n        formatOpts = DATE_SHORT;\n      }\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.isValid ? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this) : INVALID$1;\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this Interval.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @param {Object} opts - The same options as {@link DateTime#toISO}\n     * @return {string}\n     */;\n    _proto.toISO = function toISO(opts) {\n      if (!this.isValid) return INVALID$1;\n      return this.s.toISO(opts) + \"/\" + this.e.toISO(opts);\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of date of this Interval.\n     * The time components are ignored.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @return {string}\n     */;\n    _proto.toISODate = function toISODate() {\n      if (!this.isValid) return INVALID$1;\n      return this.s.toISODate() + \"/\" + this.e.toISODate();\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of time of this Interval.\n     * The date components are ignored.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @param {Object} opts - The same options as {@link DateTime#toISO}\n     * @return {string}\n     */;\n    _proto.toISOTime = function toISOTime(opts) {\n      if (!this.isValid) return INVALID$1;\n      return this.s.toISOTime(opts) + \"/\" + this.e.toISOTime(opts);\n    }\n\n    /**\n     * Returns a string representation of this Interval formatted according to the specified format\n     * string. **You may not want this.** See {@link Interval#toLocaleString} for a more flexible\n     * formatting tool.\n     * @param {string} dateFormat - The format string. This string formats the start and end time.\n     * See {@link DateTime#toFormat} for details.\n     * @param {Object} opts - Options.\n     * @param {string} [opts.separator =  ' \u2013 '] - A separator to place between the start and end\n     * representations.\n     * @return {string}\n     */;\n    _proto.toFormat = function toFormat(dateFormat, _temp2) {\n      var _ref3 = _temp2 === void 0 ? {} : _temp2,\n        _ref3$separator = _ref3.separator,\n        separator = _ref3$separator === void 0 ? \" \u2013 \" : _ref3$separator;\n      if (!this.isValid) return INVALID$1;\n      return \"\" + this.s.toFormat(dateFormat) + separator + this.e.toFormat(dateFormat);\n    }\n\n    /**\n     * Return a Duration representing the time spanned by this interval.\n     * @param {string|string[]} [unit=['milliseconds']] - the unit or units (such as 'hours' or 'days') to include in the duration.\n     * @param {Object} opts - options that affect the creation of the Duration\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration().toObject() //=> { milliseconds: 88489257 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration('days').toObject() //=> { days: 1.0241812152777778 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes']).toObject() //=> { hours: 24, minutes: 34.82095 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes', 'seconds']).toObject() //=> { hours: 24, minutes: 34, seconds: 49.257 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration('seconds').toObject() //=> { seconds: 88489.257 }\n     * @return {Duration}\n     */;\n    _proto.toDuration = function toDuration(unit, opts) {\n      if (!this.isValid) {\n        return Duration.invalid(this.invalidReason);\n      }\n      return this.e.diff(this.s, unit, opts);\n    }\n\n    /**\n     * Run mapFn on the interval start and end, returning a new Interval from the resulting DateTimes\n     * @param {function} mapFn\n     * @return {Interval}\n     * @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.toUTC())\n     * @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.plus({ hours: 2 }))\n     */;\n    _proto.mapEndpoints = function mapEndpoints(mapFn) {\n      return Interval.fromDateTimes(mapFn(this.s), mapFn(this.e));\n    };\n    _createClass(Interval, [{\n      key: \"start\",\n      get: function get() {\n        return this.isValid ? this.s : null;\n      }\n\n      /**\n       * Returns the end of the Interval\n       * @type {DateTime}\n       */\n    }, {\n      key: \"end\",\n      get: function get() {\n        return this.isValid ? this.e : null;\n      }\n\n      /**\n       * Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.\n       * @type {boolean}\n       */\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return this.invalidReason === null;\n      }\n\n      /**\n       * Returns an error code if this Interval is invalid, or null if the Interval is valid\n       * @type {string}\n       */\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.invalid ? this.invalid.reason : null;\n      }\n\n      /**\n       * Returns an explanation of why this Interval became invalid, or null if the Interval is valid\n       * @type {string}\n       */\n    }, {\n      key: \"invalidExplanation\",\n      get: function get() {\n        return this.invalid ? this.invalid.explanation : null;\n      }\n    }]);\n    return Interval;\n  }(Symbol.for(\"nodejs.util.inspect.custom\"));\n\n  /**\n   * The Info class contains static methods for retrieving general time and date related data. For example, it has methods for finding out if a time zone has a DST, for listing the months in any supported locale, and for discovering which of Luxon features are available in the current environment.\n   */\n  var Info = /*#__PURE__*/function () {\n    function Info() {}\n    /**\n     * Return whether the specified zone contains a DST.\n     * @param {string|Zone} [zone='local'] - Zone to check. Defaults to the environment's local zone.\n     * @return {boolean}\n     */\n    Info.hasDST = function hasDST(zone) {\n      if (zone === void 0) {\n        zone = Settings.defaultZone;\n      }\n      var proto = DateTime.now().setZone(zone).set({\n        month: 12\n      });\n      return !zone.isUniversal && proto.offset !== proto.set({\n        month: 6\n      }).offset;\n    }\n\n    /**\n     * Return whether the specified zone is a valid IANA specifier.\n     * @param {string} zone - Zone to check\n     * @return {boolean}\n     */;\n    Info.isValidIANAZone = function isValidIANAZone(zone) {\n      return IANAZone.isValidZone(zone);\n    }\n\n    /**\n     * Converts the input into a {@link Zone} instance.\n     *\n     * * If `input` is already a Zone instance, it is returned unchanged.\n     * * If `input` is a string containing a valid time zone name, a Zone instance\n     *   with that name is returned.\n     * * If `input` is a string that doesn't refer to a known time zone, a Zone\n     *   instance with {@link Zone#isValid} == false is returned.\n     * * If `input is a number, a Zone instance with the specified fixed offset\n     *   in minutes is returned.\n     * * If `input` is `null` or `undefined`, the default zone is returned.\n     * @param {string|Zone|number} [input] - the value to be converted\n     * @return {Zone}\n     */;\n    Info.normalizeZone = function normalizeZone$1(input) {\n      return normalizeZone(input, Settings.defaultZone);\n    }\n\n    /**\n     * Get the weekday on which the week starts according to the given locale.\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @returns {number} the start of the week, 1 for Monday through 7 for Sunday\n     */;\n    Info.getStartOfWeek = function getStartOfWeek(_temp) {\n      var _ref = _temp === void 0 ? {} : _temp,\n        _ref$locale = _ref.locale,\n        locale = _ref$locale === void 0 ? null : _ref$locale,\n        _ref$locObj = _ref.locObj,\n        locObj = _ref$locObj === void 0 ? null : _ref$locObj;\n      return (locObj || Locale.create(locale)).getStartOfWeek();\n    }\n\n    /**\n     * Get the minimum number of days necessary in a week before it is considered part of the next year according\n     * to the given locale.\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @returns {number}\n     */;\n    Info.getMinimumDaysInFirstWeek = function getMinimumDaysInFirstWeek(_temp2) {\n      var _ref2 = _temp2 === void 0 ? {} : _temp2,\n        _ref2$locale = _ref2.locale,\n        locale = _ref2$locale === void 0 ? null : _ref2$locale,\n        _ref2$locObj = _ref2.locObj,\n        locObj = _ref2$locObj === void 0 ? null : _ref2$locObj;\n      return (locObj || Locale.create(locale)).getMinDaysInFirstWeek();\n    }\n\n    /**\n     * Get the weekdays, which are considered the weekend according to the given locale\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @returns {number[]} an array of weekdays, 1 for Monday through 7 for Sunday\n     */;\n    Info.getWeekendWeekdays = function getWeekendWeekdays(_temp3) {\n      var _ref3 = _temp3 === void 0 ? {} : _temp3,\n        _ref3$locale = _ref3.locale,\n        locale = _ref3$locale === void 0 ? null : _ref3$locale,\n        _ref3$locObj = _ref3.locObj,\n        locObj = _ref3$locObj === void 0 ? null : _ref3$locObj;\n      // copy the array, because we cache it internally\n      return (locObj || Locale.create(locale)).getWeekendDays().slice();\n    }\n\n    /**\n     * Return an array of standalone month names.\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param {string} [length='long'] - the length of the month representation, such as \"numeric\", \"2-digit\", \"narrow\", \"short\", \"long\"\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @param {string} [opts.outputCalendar='gregory'] - the calendar\n     * @example Info.months()[0] //=> 'January'\n     * @example Info.months('short')[0] //=> 'Jan'\n     * @example Info.months('numeric')[0] //=> '1'\n     * @example Info.months('short', { locale: 'fr-CA' } )[0] //=> 'janv.'\n     * @example Info.months('numeric', { locale: 'ar' })[0] //=> '\u0661'\n     * @example Info.months('long', { outputCalendar: 'islamic' })[0] //=> 'Rabi\u02bb I'\n     * @return {Array}\n     */;\n    Info.months = function months(length, _temp4) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n      var _ref4 = _temp4 === void 0 ? {} : _temp4,\n        _ref4$locale = _ref4.locale,\n        locale = _ref4$locale === void 0 ? null : _ref4$locale,\n        _ref4$numberingSystem = _ref4.numberingSystem,\n        numberingSystem = _ref4$numberingSystem === void 0 ? null : _ref4$numberingSystem,\n        _ref4$locObj = _ref4.locObj,\n        locObj = _ref4$locObj === void 0 ? null : _ref4$locObj,\n        _ref4$outputCalendar = _ref4.outputCalendar,\n        outputCalendar = _ref4$outputCalendar === void 0 ? \"gregory\" : _ref4$outputCalendar;\n      return (locObj || Locale.create(locale, numberingSystem, outputCalendar)).months(length);\n    }\n\n    /**\n     * Return an array of format month names.\n     * Format months differ from standalone months in that they're meant to appear next to the day of the month. In some languages, that\n     * changes the string.\n     * See {@link Info#months}\n     * @param {string} [length='long'] - the length of the month representation, such as \"numeric\", \"2-digit\", \"narrow\", \"short\", \"long\"\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @param {string} [opts.outputCalendar='gregory'] - the calendar\n     * @return {Array}\n     */;\n    Info.monthsFormat = function monthsFormat(length, _temp5) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n      var _ref5 = _temp5 === void 0 ? {} : _temp5,\n        _ref5$locale = _ref5.locale,\n        locale = _ref5$locale === void 0 ? null : _ref5$locale,\n        _ref5$numberingSystem = _ref5.numberingSystem,\n        numberingSystem = _ref5$numberingSystem === void 0 ? null : _ref5$numberingSystem,\n        _ref5$locObj = _ref5.locObj,\n        locObj = _ref5$locObj === void 0 ? null : _ref5$locObj,\n        _ref5$outputCalendar = _ref5.outputCalendar,\n        outputCalendar = _ref5$outputCalendar === void 0 ? \"gregory\" : _ref5$outputCalendar;\n      return (locObj || Locale.create(locale, numberingSystem, outputCalendar)).months(length, true);\n    }\n\n    /**\n     * Return an array of standalone week names.\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param {string} [length='long'] - the length of the weekday representation, such as \"narrow\", \"short\", \"long\".\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @example Info.weekdays()[0] //=> 'Monday'\n     * @example Info.weekdays('short')[0] //=> 'Mon'\n     * @example Info.weekdays('short', { locale: 'fr-CA' })[0] //=> 'lun.'\n     * @example Info.weekdays('short', { locale: 'ar' })[0] //=> '\u0627\u0644\u0627\u062b\u0646\u064a\u0646'\n     * @return {Array}\n     */;\n    Info.weekdays = function weekdays(length, _temp6) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n      var _ref6 = _temp6 === void 0 ? {} : _temp6,\n        _ref6$locale = _ref6.locale,\n        locale = _ref6$locale === void 0 ? null : _ref6$locale,\n        _ref6$numberingSystem = _ref6.numberingSystem,\n        numberingSystem = _ref6$numberingSystem === void 0 ? null : _ref6$numberingSystem,\n        _ref6$locObj = _ref6.locObj,\n        locObj = _ref6$locObj === void 0 ? null : _ref6$locObj;\n      return (locObj || Locale.create(locale, numberingSystem, null)).weekdays(length);\n    }\n\n    /**\n     * Return an array of format week names.\n     * Format weekdays differ from standalone weekdays in that they're meant to appear next to more date information. In some languages, that\n     * changes the string.\n     * See {@link Info#weekdays}\n     * @param {string} [length='long'] - the length of the month representation, such as \"narrow\", \"short\", \"long\".\n     * @param {Object} opts - options\n     * @param {string} [opts.locale=null] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @param {string} [opts.locObj=null] - an existing locale object to use\n     * @return {Array}\n     */;\n    Info.weekdaysFormat = function weekdaysFormat(length, _temp7) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n      var _ref7 = _temp7 === void 0 ? {} : _temp7,\n        _ref7$locale = _ref7.locale,\n        locale = _ref7$locale === void 0 ? null : _ref7$locale,\n        _ref7$numberingSystem = _ref7.numberingSystem,\n        numberingSystem = _ref7$numberingSystem === void 0 ? null : _ref7$numberingSystem,\n        _ref7$locObj = _ref7.locObj,\n        locObj = _ref7$locObj === void 0 ? null : _ref7$locObj;\n      return (locObj || Locale.create(locale, numberingSystem, null)).weekdays(length, true);\n    }\n\n    /**\n     * Return an array of meridiems.\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @example Info.meridiems() //=> [ 'AM', 'PM' ]\n     * @example Info.meridiems({ locale: 'my' }) //=> [ '\u1014\u1036\u1014\u1000\u103a', '\u100a\u1014\u1031' ]\n     * @return {Array}\n     */;\n    Info.meridiems = function meridiems(_temp8) {\n      var _ref8 = _temp8 === void 0 ? {} : _temp8,\n        _ref8$locale = _ref8.locale,\n        locale = _ref8$locale === void 0 ? null : _ref8$locale;\n      return Locale.create(locale).meridiems();\n    }\n\n    /**\n     * Return an array of eras, such as ['BC', 'AD']. The locale can be specified, but the calendar system is always Gregorian.\n     * @param {string} [length='short'] - the length of the era representation, such as \"short\" or \"long\".\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @example Info.eras() //=> [ 'BC', 'AD' ]\n     * @example Info.eras('long') //=> [ 'Before Christ', 'Anno Domini' ]\n     * @example Info.eras('long', { locale: 'fr' }) //=> [ 'avant J\u00e9sus-Christ', 'apr\u00e8s J\u00e9sus-Christ' ]\n     * @return {Array}\n     */;\n    Info.eras = function eras(length, _temp9) {\n      if (length === void 0) {\n        length = \"short\";\n      }\n      var _ref9 = _temp9 === void 0 ? {} : _temp9,\n        _ref9$locale = _ref9.locale,\n        locale = _ref9$locale === void 0 ? null : _ref9$locale;\n      return Locale.create(locale, null, \"gregory\").eras(length);\n    }\n\n    /**\n     * Return the set of available features in this environment.\n     * Some features of Luxon are not available in all environments. For example, on older browsers, relative time formatting support is not available. Use this function to figure out if that's the case.\n     * Keys:\n     * * `relative`: whether this environment supports relative time formatting\n     * * `localeWeek`: whether this environment supports different weekdays for the start of the week based on the locale\n     * @example Info.features() //=> { relative: false, localeWeek: true }\n     * @return {Object}\n     */;\n    Info.features = function features() {\n      return {\n        relative: hasRelative(),\n        localeWeek: hasLocaleWeekInfo()\n      };\n    };\n    return Info;\n  }();\n\n  function dayDiff(earlier, later) {\n    var utcDayStart = function utcDayStart(dt) {\n        return dt.toUTC(0, {\n          keepLocalTime: true\n        }).startOf(\"day\").valueOf();\n      },\n      ms = utcDayStart(later) - utcDayStart(earlier);\n    return Math.floor(Duration.fromMillis(ms).as(\"days\"));\n  }\n  function highOrderDiffs(cursor, later, units) {\n    var differs = [[\"years\", function (a, b) {\n      return b.year - a.year;\n    }], [\"quarters\", function (a, b) {\n      return b.quarter - a.quarter + (b.year - a.year) * 4;\n    }], [\"months\", function (a, b) {\n      return b.month - a.month + (b.year - a.year) * 12;\n    }], [\"weeks\", function (a, b) {\n      var days = dayDiff(a, b);\n      return (days - days % 7) / 7;\n    }], [\"days\", dayDiff]];\n    var results = {};\n    var earlier = cursor;\n    var lowestOrder, highWater;\n\n    /* This loop tries to diff using larger units first.\n       If we overshoot, we backtrack and try the next smaller unit.\n       \"cursor\" starts out at the earlier timestamp and moves closer and closer to \"later\"\n       as we use smaller and smaller units.\n       highWater keeps track of where we would be if we added one more of the smallest unit,\n       this is used later to potentially convert any difference smaller than the smallest higher order unit\n       into a fraction of that smallest higher order unit\n    */\n    for (var _i = 0, _differs = differs; _i < _differs.length; _i++) {\n      var _differs$_i = _differs[_i],\n        unit = _differs$_i[0],\n        differ = _differs$_i[1];\n      if (units.indexOf(unit) >= 0) {\n        lowestOrder = unit;\n        results[unit] = differ(cursor, later);\n        highWater = earlier.plus(results);\n        if (highWater > later) {\n          // we overshot the end point, backtrack cursor by 1\n          results[unit]--;\n          cursor = earlier.plus(results);\n\n          // if we are still overshooting now, we need to backtrack again\n          // this happens in certain situations when diffing times in different zones,\n          // because this calculation ignores time zones\n          if (cursor > later) {\n            // keep the \"overshot by 1\" around as highWater\n            highWater = cursor;\n            // backtrack cursor by 1\n            results[unit]--;\n            cursor = earlier.plus(results);\n          }\n        } else {\n          cursor = highWater;\n        }\n      }\n    }\n    return [cursor, results, highWater, lowestOrder];\n  }\n  function _diff (earlier, later, units, opts) {\n    var _highOrderDiffs = highOrderDiffs(earlier, later, units),\n      cursor = _highOrderDiffs[0],\n      results = _highOrderDiffs[1],\n      highWater = _highOrderDiffs[2],\n      lowestOrder = _highOrderDiffs[3];\n    var remainingMillis = later - cursor;\n    var lowerOrderUnits = units.filter(function (u) {\n      return [\"hours\", \"minutes\", \"seconds\", \"milliseconds\"].indexOf(u) >= 0;\n    });\n    if (lowerOrderUnits.length === 0) {\n      if (highWater < later) {\n        var _cursor$plus;\n        highWater = cursor.plus((_cursor$plus = {}, _cursor$plus[lowestOrder] = 1, _cursor$plus));\n      }\n      if (highWater !== cursor) {\n        results[lowestOrder] = (results[lowestOrder] || 0) + remainingMillis / (highWater - cursor);\n      }\n    }\n    var duration = Duration.fromObject(results, opts);\n    if (lowerOrderUnits.length > 0) {\n      var _Duration$fromMillis;\n      return (_Duration$fromMillis = Duration.fromMillis(remainingMillis, opts)).shiftTo.apply(_Duration$fromMillis, lowerOrderUnits).plus(duration);\n    } else {\n      return duration;\n    }\n  }\n\n  var MISSING_FTP = \"missing Intl.DateTimeFormat.formatToParts support\";\n  function intUnit(regex, post) {\n    if (post === void 0) {\n      post = function post(i) {\n        return i;\n      };\n    }\n    return {\n      regex: regex,\n      deser: function deser(_ref) {\n        var s = _ref[0];\n        return post(parseDigits(s));\n      }\n    };\n  }\n  var NBSP = String.fromCharCode(160);\n  var spaceOrNBSP = \"[ \" + NBSP + \"]\";\n  var spaceOrNBSPRegExp = new RegExp(spaceOrNBSP, \"g\");\n  function fixListRegex(s) {\n    // make dots optional and also make them literal\n    // make space and non breakable space characters interchangeable\n    return s.replace(/\\./g, \"\\\\.?\").replace(spaceOrNBSPRegExp, spaceOrNBSP);\n  }\n  function stripInsensitivities(s) {\n    return s.replace(/\\./g, \"\") // ignore dots that were made optional\n    .replace(spaceOrNBSPRegExp, \" \") // interchange space and nbsp\n    .toLowerCase();\n  }\n  function oneOf(strings, startIndex) {\n    if (strings === null) {\n      return null;\n    } else {\n      return {\n        regex: RegExp(strings.map(fixListRegex).join(\"|\")),\n        deser: function deser(_ref2) {\n          var s = _ref2[0];\n          return strings.findIndex(function (i) {\n            return stripInsensitivities(s) === stripInsensitivities(i);\n          }) + startIndex;\n        }\n      };\n    }\n  }\n  function offset(regex, groups) {\n    return {\n      regex: regex,\n      deser: function deser(_ref3) {\n        var h = _ref3[1],\n          m = _ref3[2];\n        return signedOffset(h, m);\n      },\n      groups: groups\n    };\n  }\n  function simple(regex) {\n    return {\n      regex: regex,\n      deser: function deser(_ref4) {\n        var s = _ref4[0];\n        return s;\n      }\n    };\n  }\n  function escapeToken(value) {\n    return value.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, \"\\\\$&\");\n  }\n\n  /**\n   * @param token\n   * @param {Locale} loc\n   */\n  function unitForToken(token, loc) {\n    var one = digitRegex(loc),\n      two = digitRegex(loc, \"{2}\"),\n      three = digitRegex(loc, \"{3}\"),\n      four = digitRegex(loc, \"{4}\"),\n      six = digitRegex(loc, \"{6}\"),\n      oneOrTwo = digitRegex(loc, \"{1,2}\"),\n      oneToThree = digitRegex(loc, \"{1,3}\"),\n      oneToSix = digitRegex(loc, \"{1,6}\"),\n      oneToNine = digitRegex(loc, \"{1,9}\"),\n      twoToFour = digitRegex(loc, \"{2,4}\"),\n      fourToSix = digitRegex(loc, \"{4,6}\"),\n      literal = function literal(t) {\n        return {\n          regex: RegExp(escapeToken(t.val)),\n          deser: function deser(_ref5) {\n            var s = _ref5[0];\n            return s;\n          },\n          literal: true\n        };\n      },\n      unitate = function unitate(t) {\n        if (token.literal) {\n          return literal(t);\n        }\n        switch (t.val) {\n          // era\n          case \"G\":\n            return oneOf(loc.eras(\"short\"), 0);\n          case \"GG\":\n            return oneOf(loc.eras(\"long\"), 0);\n          // years\n          case \"y\":\n            return intUnit(oneToSix);\n          case \"yy\":\n            return intUnit(twoToFour, untruncateYear);\n          case \"yyyy\":\n            return intUnit(four);\n          case \"yyyyy\":\n            return intUnit(fourToSix);\n          case \"yyyyyy\":\n            return intUnit(six);\n          // months\n          case \"M\":\n            return intUnit(oneOrTwo);\n          case \"MM\":\n            return intUnit(two);\n          case \"MMM\":\n            return oneOf(loc.months(\"short\", true), 1);\n          case \"MMMM\":\n            return oneOf(loc.months(\"long\", true), 1);\n          case \"L\":\n            return intUnit(oneOrTwo);\n          case \"LL\":\n            return intUnit(two);\n          case \"LLL\":\n            return oneOf(loc.months(\"short\", false), 1);\n          case \"LLLL\":\n            return oneOf(loc.months(\"long\", false), 1);\n          // dates\n          case \"d\":\n            return intUnit(oneOrTwo);\n          case \"dd\":\n            return intUnit(two);\n          // ordinals\n          case \"o\":\n            return intUnit(oneToThree);\n          case \"ooo\":\n            return intUnit(three);\n          // time\n          case \"HH\":\n            return intUnit(two);\n          case \"H\":\n            return intUnit(oneOrTwo);\n          case \"hh\":\n            return intUnit(two);\n          case \"h\":\n            return intUnit(oneOrTwo);\n          case \"mm\":\n            return intUnit(two);\n          case \"m\":\n            return intUnit(oneOrTwo);\n          case \"q\":\n            return intUnit(oneOrTwo);\n          case \"qq\":\n            return intUnit(two);\n          case \"s\":\n            return intUnit(oneOrTwo);\n          case \"ss\":\n            return intUnit(two);\n          case \"S\":\n            return intUnit(oneToThree);\n          case \"SSS\":\n            return intUnit(three);\n          case \"u\":\n            return simple(oneToNine);\n          case \"uu\":\n            return simple(oneOrTwo);\n          case \"uuu\":\n            return intUnit(one);\n          // meridiem\n          case \"a\":\n            return oneOf(loc.meridiems(), 0);\n          // weekYear (k)\n          case \"kkkk\":\n            return intUnit(four);\n          case \"kk\":\n            return intUnit(twoToFour, untruncateYear);\n          // weekNumber (W)\n          case \"W\":\n            return intUnit(oneOrTwo);\n          case \"WW\":\n            return intUnit(two);\n          // weekdays\n          case \"E\":\n          case \"c\":\n            return intUnit(one);\n          case \"EEE\":\n            return oneOf(loc.weekdays(\"short\", false), 1);\n          case \"EEEE\":\n            return oneOf(loc.weekdays(\"long\", false), 1);\n          case \"ccc\":\n            return oneOf(loc.weekdays(\"short\", true), 1);\n          case \"cccc\":\n            return oneOf(loc.weekdays(\"long\", true), 1);\n          // offset/zone\n          case \"Z\":\n          case \"ZZ\":\n            return offset(new RegExp(\"([+-]\" + oneOrTwo.source + \")(?::(\" + two.source + \"))?\"), 2);\n          case \"ZZZ\":\n            return offset(new RegExp(\"([+-]\" + oneOrTwo.source + \")(\" + two.source + \")?\"), 2);\n          // we don't support ZZZZ (PST) or ZZZZZ (Pacific Standard Time) in parsing\n          // because we don't have any way to figure out what they are\n          case \"z\":\n            return simple(/[a-z_+-/]{1,256}?/i);\n          // this special-case \"token\" represents a place where a macro-token expanded into a white-space literal\n          // in this case we accept any non-newline white-space\n          case \" \":\n            return simple(/[^\\S\\n\\r]/);\n          default:\n            return literal(t);\n        }\n      };\n    var unit = unitate(token) || {\n      invalidReason: MISSING_FTP\n    };\n    unit.token = token;\n    return unit;\n  }\n  var partTypeStyleToTokenVal = {\n    year: {\n      \"2-digit\": \"yy\",\n      numeric: \"yyyyy\"\n    },\n    month: {\n      numeric: \"M\",\n      \"2-digit\": \"MM\",\n      short: \"MMM\",\n      long: \"MMMM\"\n    },\n    day: {\n      numeric: \"d\",\n      \"2-digit\": \"dd\"\n    },\n    weekday: {\n      short: \"EEE\",\n      long: \"EEEE\"\n    },\n    dayperiod: \"a\",\n    dayPeriod: \"a\",\n    hour12: {\n      numeric: \"h\",\n      \"2-digit\": \"hh\"\n    },\n    hour24: {\n      numeric: \"H\",\n      \"2-digit\": \"HH\"\n    },\n    minute: {\n      numeric: \"m\",\n      \"2-digit\": \"mm\"\n    },\n    second: {\n      numeric: \"s\",\n      \"2-digit\": \"ss\"\n    },\n    timeZoneName: {\n      long: \"ZZZZZ\",\n      short: \"ZZZ\"\n    }\n  };\n  function tokenForPart(part, formatOpts, resolvedOpts) {\n    var type = part.type,\n      value = part.value;\n    if (type === \"literal\") {\n      var isSpace = /^\\s+$/.test(value);\n      return {\n        literal: !isSpace,\n        val: isSpace ? \" \" : value\n      };\n    }\n    var style = formatOpts[type];\n\n    // The user might have explicitly specified hour12 or hourCycle\n    // if so, respect their decision\n    // if not, refer back to the resolvedOpts, which are based on the locale\n    var actualType = type;\n    if (type === \"hour\") {\n      if (formatOpts.hour12 != null) {\n        actualType = formatOpts.hour12 ? \"hour12\" : \"hour24\";\n      } else if (formatOpts.hourCycle != null) {\n        if (formatOpts.hourCycle === \"h11\" || formatOpts.hourCycle === \"h12\") {\n          actualType = \"hour12\";\n        } else {\n          actualType = \"hour24\";\n        }\n      } else {\n        // tokens only differentiate between 24 hours or not,\n        // so we do not need to check hourCycle here, which is less supported anyways\n        actualType = resolvedOpts.hour12 ? \"hour12\" : \"hour24\";\n      }\n    }\n    var val = partTypeStyleToTokenVal[actualType];\n    if (typeof val === \"object\") {\n      val = val[style];\n    }\n    if (val) {\n      return {\n        literal: false,\n        val: val\n      };\n    }\n    return undefined;\n  }\n  function buildRegex(units) {\n    var re = units.map(function (u) {\n      return u.regex;\n    }).reduce(function (f, r) {\n      return f + \"(\" + r.source + \")\";\n    }, \"\");\n    return [\"^\" + re + \"$\", units];\n  }\n  function match(input, regex, handlers) {\n    var matches = input.match(regex);\n    if (matches) {\n      var all = {};\n      var matchIndex = 1;\n      for (var i in handlers) {\n        if (hasOwnProperty(handlers, i)) {\n          var h = handlers[i],\n            groups = h.groups ? h.groups + 1 : 1;\n          if (!h.literal && h.token) {\n            all[h.token.val[0]] = h.deser(matches.slice(matchIndex, matchIndex + groups));\n          }\n          matchIndex += groups;\n        }\n      }\n      return [matches, all];\n    } else {\n      return [matches, {}];\n    }\n  }\n  function dateTimeFromMatches(matches) {\n    var toField = function toField(token) {\n      switch (token) {\n        case \"S\":\n          return \"millisecond\";\n        case \"s\":\n          return \"second\";\n        case \"m\":\n          return \"minute\";\n        case \"h\":\n        case \"H\":\n          return \"hour\";\n        case \"d\":\n          return \"day\";\n        case \"o\":\n          return \"ordinal\";\n        case \"L\":\n        case \"M\":\n          return \"month\";\n        case \"y\":\n          return \"year\";\n        case \"E\":\n        case \"c\":\n          return \"weekday\";\n        case \"W\":\n          return \"weekNumber\";\n        case \"k\":\n          return \"weekYear\";\n        case \"q\":\n          return \"quarter\";\n        default:\n          return null;\n      }\n    };\n    var zone = null;\n    var specificOffset;\n    if (!isUndefined(matches.z)) {\n      zone = IANAZone.create(matches.z);\n    }\n    if (!isUndefined(matches.Z)) {\n      if (!zone) {\n        zone = new FixedOffsetZone(matches.Z);\n      }\n      specificOffset = matches.Z;\n    }\n    if (!isUndefined(matches.q)) {\n      matches.M = (matches.q - 1) * 3 + 1;\n    }\n    if (!isUndefined(matches.h)) {\n      if (matches.h < 12 && matches.a === 1) {\n        matches.h += 12;\n      } else if (matches.h === 12 && matches.a === 0) {\n        matches.h = 0;\n      }\n    }\n    if (matches.G === 0 && matches.y) {\n      matches.y = -matches.y;\n    }\n    if (!isUndefined(matches.u)) {\n      matches.S = parseMillis(matches.u);\n    }\n    var vals = Object.keys(matches).reduce(function (r, k) {\n      var f = toField(k);\n      if (f) {\n        r[f] = matches[k];\n      }\n      return r;\n    }, {});\n    return [vals, zone, specificOffset];\n  }\n  var dummyDateTimeCache = null;\n  function getDummyDateTime() {\n    if (!dummyDateTimeCache) {\n      dummyDateTimeCache = DateTime.fromMillis(1555555555555);\n    }\n    return dummyDateTimeCache;\n  }\n  function maybeExpandMacroToken(token, locale) {\n    if (token.literal) {\n      return token;\n    }\n    var formatOpts = Formatter.macroTokenToFormatOpts(token.val);\n    var tokens = formatOptsToTokens(formatOpts, locale);\n    if (tokens == null || tokens.includes(undefined)) {\n      return token;\n    }\n    return tokens;\n  }\n  function expandMacroTokens(tokens, locale) {\n    var _Array$prototype;\n    return (_Array$prototype = Array.prototype).concat.apply(_Array$prototype, tokens.map(function (t) {\n      return maybeExpandMacroToken(t, locale);\n    }));\n  }\n\n  /**\n   * @private\n   */\n\n  var TokenParser = /*#__PURE__*/function () {\n    function TokenParser(locale, format) {\n      this.locale = locale;\n      this.format = format;\n      this.tokens = expandMacroTokens(Formatter.parseFormat(format), locale);\n      this.units = this.tokens.map(function (t) {\n        return unitForToken(t, locale);\n      });\n      this.disqualifyingUnit = this.units.find(function (t) {\n        return t.invalidReason;\n      });\n      if (!this.disqualifyingUnit) {\n        var _buildRegex = buildRegex(this.units),\n          regexString = _buildRegex[0],\n          handlers = _buildRegex[1];\n        this.regex = RegExp(regexString, \"i\");\n        this.handlers = handlers;\n      }\n    }\n    var _proto = TokenParser.prototype;\n    _proto.explainFromTokens = function explainFromTokens(input) {\n      if (!this.isValid) {\n        return {\n          input: input,\n          tokens: this.tokens,\n          invalidReason: this.invalidReason\n        };\n      } else {\n        var _match = match(input, this.regex, this.handlers),\n          rawMatches = _match[0],\n          matches = _match[1],\n          _ref6 = matches ? dateTimeFromMatches(matches) : [null, null, undefined],\n          result = _ref6[0],\n          zone = _ref6[1],\n          specificOffset = _ref6[2];\n        if (hasOwnProperty(matches, \"a\") && hasOwnProperty(matches, \"H\")) {\n          throw new ConflictingSpecificationError(\"Can't include meridiem when specifying 24-hour format\");\n        }\n        return {\n          input: input,\n          tokens: this.tokens,\n          regex: this.regex,\n          rawMatches: rawMatches,\n          matches: matches,\n          result: result,\n          zone: zone,\n          specificOffset: specificOffset\n        };\n      }\n    };\n    _createClass(TokenParser, [{\n      key: \"isValid\",\n      get: function get() {\n        return !this.disqualifyingUnit;\n      }\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.disqualifyingUnit ? this.disqualifyingUnit.invalidReason : null;\n      }\n    }]);\n    return TokenParser;\n  }();\n  function explainFromTokens(locale, input, format) {\n    var parser = new TokenParser(locale, format);\n    return parser.explainFromTokens(input);\n  }\n  function parseFromTokens(locale, input, format) {\n    var _explainFromTokens = explainFromTokens(locale, input, format),\n      result = _explainFromTokens.result,\n      zone = _explainFromTokens.zone,\n      specificOffset = _explainFromTokens.specificOffset,\n      invalidReason = _explainFromTokens.invalidReason;\n    return [result, zone, specificOffset, invalidReason];\n  }\n  function formatOptsToTokens(formatOpts, locale) {\n    if (!formatOpts) {\n      return null;\n    }\n    var formatter = Formatter.create(locale, formatOpts);\n    var df = formatter.dtFormatter(getDummyDateTime());\n    var parts = df.formatToParts();\n    var resolvedOpts = df.resolvedOptions();\n    return parts.map(function (p) {\n      return tokenForPart(p, formatOpts, resolvedOpts);\n    });\n  }\n\n  var INVALID = \"Invalid DateTime\";\n  var MAX_DATE = 8.64e15;\n  function unsupportedZone(zone) {\n    return new Invalid(\"unsupported zone\", \"the zone \\\"\" + zone.name + \"\\\" is not supported\");\n  }\n\n  // we cache week data on the DT object and this intermediates the cache\n  /**\n   * @param {DateTime} dt\n   */\n  function possiblyCachedWeekData(dt) {\n    if (dt.weekData === null) {\n      dt.weekData = gregorianToWeek(dt.c);\n    }\n    return dt.weekData;\n  }\n\n  /**\n   * @param {DateTime} dt\n   */\n  function possiblyCachedLocalWeekData(dt) {\n    if (dt.localWeekData === null) {\n      dt.localWeekData = gregorianToWeek(dt.c, dt.loc.getMinDaysInFirstWeek(), dt.loc.getStartOfWeek());\n    }\n    return dt.localWeekData;\n  }\n\n  // clone really means, \"make a new object with these modifications\". all \"setters\" really use this\n  // to create a new object while only changing some of the properties\n  function clone(inst, alts) {\n    var current = {\n      ts: inst.ts,\n      zone: inst.zone,\n      c: inst.c,\n      o: inst.o,\n      loc: inst.loc,\n      invalid: inst.invalid\n    };\n    return new DateTime(_extends({}, current, alts, {\n      old: current\n    }));\n  }\n\n  // find the right offset a given local time. The o input is our guess, which determines which\n  // offset we'll pick in ambiguous cases (e.g. there are two 3 AMs b/c Fallback DST)\n  function fixOffset(localTS, o, tz) {\n    // Our UTC time is just a guess because our offset is just a guess\n    var utcGuess = localTS - o * 60 * 1000;\n\n    // Test whether the zone matches the offset for this ts\n    var o2 = tz.offset(utcGuess);\n\n    // If so, offset didn't change and we're done\n    if (o === o2) {\n      return [utcGuess, o];\n    }\n\n    // If not, change the ts by the difference in the offset\n    utcGuess -= (o2 - o) * 60 * 1000;\n\n    // If that gives us the local time we want, we're done\n    var o3 = tz.offset(utcGuess);\n    if (o2 === o3) {\n      return [utcGuess, o2];\n    }\n\n    // If it's different, we're in a hole time. The offset has changed, but the we don't adjust the time\n    return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3)];\n  }\n\n  // convert an epoch timestamp into a calendar object with the given offset\n  function tsToObj(ts, offset) {\n    ts += offset * 60 * 1000;\n    var d = new Date(ts);\n    return {\n      year: d.getUTCFullYear(),\n      month: d.getUTCMonth() + 1,\n      day: d.getUTCDate(),\n      hour: d.getUTCHours(),\n      minute: d.getUTCMinutes(),\n      second: d.getUTCSeconds(),\n      millisecond: d.getUTCMilliseconds()\n    };\n  }\n\n  // convert a calendar object to a epoch timestamp\n  function objToTS(obj, offset, zone) {\n    return fixOffset(objToLocalTS(obj), offset, zone);\n  }\n\n  // create a new DT instance by adding a duration, adjusting for DSTs\n  function adjustTime(inst, dur) {\n    var oPre = inst.o,\n      year = inst.c.year + Math.trunc(dur.years),\n      month = inst.c.month + Math.trunc(dur.months) + Math.trunc(dur.quarters) * 3,\n      c = _extends({}, inst.c, {\n        year: year,\n        month: month,\n        day: Math.min(inst.c.day, daysInMonth(year, month)) + Math.trunc(dur.days) + Math.trunc(dur.weeks) * 7\n      }),\n      millisToAdd = Duration.fromObject({\n        years: dur.years - Math.trunc(dur.years),\n        quarters: dur.quarters - Math.trunc(dur.quarters),\n        months: dur.months - Math.trunc(dur.months),\n        weeks: dur.weeks - Math.trunc(dur.weeks),\n        days: dur.days - Math.trunc(dur.days),\n        hours: dur.hours,\n        minutes: dur.minutes,\n        seconds: dur.seconds,\n        milliseconds: dur.milliseconds\n      }).as(\"milliseconds\"),\n      localTS = objToLocalTS(c);\n    var _fixOffset = fixOffset(localTS, oPre, inst.zone),\n      ts = _fixOffset[0],\n      o = _fixOffset[1];\n    if (millisToAdd !== 0) {\n      ts += millisToAdd;\n      // that could have changed the offset by going over a DST, but we want to keep the ts the same\n      o = inst.zone.offset(ts);\n    }\n    return {\n      ts: ts,\n      o: o\n    };\n  }\n\n  // helper useful in turning the results of parsing into real dates\n  // by handling the zone options\n  function parseDataToDateTime(parsed, parsedZone, opts, format, text, specificOffset) {\n    var setZone = opts.setZone,\n      zone = opts.zone;\n    if (parsed && Object.keys(parsed).length !== 0 || parsedZone) {\n      var interpretationZone = parsedZone || zone,\n        inst = DateTime.fromObject(parsed, _extends({}, opts, {\n          zone: interpretationZone,\n          specificOffset: specificOffset\n        }));\n      return setZone ? inst : inst.setZone(zone);\n    } else {\n      return DateTime.invalid(new Invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as \" + format));\n    }\n  }\n\n  // if you want to output a technical format (e.g. RFC 2822), this helper\n  // helps handle the details\n  function toTechFormat(dt, format, allowZ) {\n    if (allowZ === void 0) {\n      allowZ = true;\n    }\n    return dt.isValid ? Formatter.create(Locale.create(\"en-US\"), {\n      allowZ: allowZ,\n      forceSimple: true\n    }).formatDateTimeFromString(dt, format) : null;\n  }\n  function _toISODate(o, extended) {\n    var longFormat = o.c.year > 9999 || o.c.year < 0;\n    var c = \"\";\n    if (longFormat && o.c.year >= 0) c += \"+\";\n    c += padStart(o.c.year, longFormat ? 6 : 4);\n    if (extended) {\n      c += \"-\";\n      c += padStart(o.c.month);\n      c += \"-\";\n      c += padStart(o.c.day);\n    } else {\n      c += padStart(o.c.month);\n      c += padStart(o.c.day);\n    }\n    return c;\n  }\n  function _toISOTime(o, extended, suppressSeconds, suppressMilliseconds, includeOffset, extendedZone) {\n    var c = padStart(o.c.hour);\n    if (extended) {\n      c += \":\";\n      c += padStart(o.c.minute);\n      if (o.c.millisecond !== 0 || o.c.second !== 0 || !suppressSeconds) {\n        c += \":\";\n      }\n    } else {\n      c += padStart(o.c.minute);\n    }\n    if (o.c.millisecond !== 0 || o.c.second !== 0 || !suppressSeconds) {\n      c += padStart(o.c.second);\n      if (o.c.millisecond !== 0 || !suppressMilliseconds) {\n        c += \".\";\n        c += padStart(o.c.millisecond, 3);\n      }\n    }\n    if (includeOffset) {\n      if (o.isOffsetFixed && o.offset === 0 && !extendedZone) {\n        c += \"Z\";\n      } else if (o.o < 0) {\n        c += \"-\";\n        c += padStart(Math.trunc(-o.o / 60));\n        c += \":\";\n        c += padStart(Math.trunc(-o.o % 60));\n      } else {\n        c += \"+\";\n        c += padStart(Math.trunc(o.o / 60));\n        c += \":\";\n        c += padStart(Math.trunc(o.o % 60));\n      }\n    }\n    if (extendedZone) {\n      c += \"[\" + o.zone.ianaName + \"]\";\n    }\n    return c;\n  }\n\n  // defaults for unspecified units in the supported calendars\n  var defaultUnitValues = {\n      month: 1,\n      day: 1,\n      hour: 0,\n      minute: 0,\n      second: 0,\n      millisecond: 0\n    },\n    defaultWeekUnitValues = {\n      weekNumber: 1,\n      weekday: 1,\n      hour: 0,\n      minute: 0,\n      second: 0,\n      millisecond: 0\n    },\n    defaultOrdinalUnitValues = {\n      ordinal: 1,\n      hour: 0,\n      minute: 0,\n      second: 0,\n      millisecond: 0\n    };\n\n  // Units in the supported calendars, sorted by bigness\n  var orderedUnits = [\"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\", \"millisecond\"],\n    orderedWeekUnits = [\"weekYear\", \"weekNumber\", \"weekday\", \"hour\", \"minute\", \"second\", \"millisecond\"],\n    orderedOrdinalUnits = [\"year\", \"ordinal\", \"hour\", \"minute\", \"second\", \"millisecond\"];\n\n  // standardize case and plurality in units\n  function normalizeUnit(unit) {\n    var normalized = {\n      year: \"year\",\n      years: \"year\",\n      month: \"month\",\n      months: \"month\",\n      day: \"day\",\n      days: \"day\",\n      hour: \"hour\",\n      hours: \"hour\",\n      minute: \"minute\",\n      minutes: \"minute\",\n      quarter: \"quarter\",\n      quarters: \"quarter\",\n      second: \"second\",\n      seconds: \"second\",\n      millisecond: \"millisecond\",\n      milliseconds: \"millisecond\",\n      weekday: \"weekday\",\n      weekdays: \"weekday\",\n      weeknumber: \"weekNumber\",\n      weeksnumber: \"weekNumber\",\n      weeknumbers: \"weekNumber\",\n      weekyear: \"weekYear\",\n      weekyears: \"weekYear\",\n      ordinal: \"ordinal\"\n    }[unit.toLowerCase()];\n    if (!normalized) throw new InvalidUnitError(unit);\n    return normalized;\n  }\n  function normalizeUnitWithLocalWeeks(unit) {\n    switch (unit.toLowerCase()) {\n      case \"localweekday\":\n      case \"localweekdays\":\n        return \"localWeekday\";\n      case \"localweeknumber\":\n      case \"localweeknumbers\":\n        return \"localWeekNumber\";\n      case \"localweekyear\":\n      case \"localweekyears\":\n        return \"localWeekYear\";\n      default:\n        return normalizeUnit(unit);\n    }\n  }\n\n  // cache offsets for zones based on the current timestamp when this function is\n  // first called. When we are handling a datetime from components like (year,\n  // month, day, hour) in a time zone, we need a guess about what the timezone\n  // offset is so that we can convert into a UTC timestamp. One way is to find the\n  // offset of now in the zone. The actual date may have a different offset (for\n  // example, if we handle a date in June while we're in December in a zone that\n  // observes DST), but we can check and adjust that.\n  //\n  // When handling many dates, calculating the offset for now every time is\n  // expensive. It's just a guess, so we can cache the offset to use even if we\n  // are right on a time change boundary (we'll just correct in the other\n  // direction). Using a timestamp from first read is a slight optimization for\n  // handling dates close to the current date, since those dates will usually be\n  // in the same offset (we could set the timestamp statically, instead). We use a\n  // single timestamp for all zones to make things a bit more predictable.\n  //\n  // This is safe for quickDT (used by local() and utc()) because we don't fill in\n  // higher-order units from tsNow (as we do in fromObject, this requires that\n  // offset is calculated from tsNow).\n  function guessOffsetForZone(zone) {\n    if (!zoneOffsetGuessCache[zone]) {\n      if (zoneOffsetTs === undefined) {\n        zoneOffsetTs = Settings.now();\n      }\n      zoneOffsetGuessCache[zone] = zone.offset(zoneOffsetTs);\n    }\n    return zoneOffsetGuessCache[zone];\n  }\n\n  // this is a dumbed down version of fromObject() that runs about 60% faster\n  // but doesn't do any validation, makes a bunch of assumptions about what units\n  // are present, and so on.\n  function quickDT(obj, opts) {\n    var zone = normalizeZone(opts.zone, Settings.defaultZone);\n    if (!zone.isValid) {\n      return DateTime.invalid(unsupportedZone(zone));\n    }\n    var loc = Locale.fromObject(opts);\n    var ts, o;\n\n    // assume we have the higher-order units\n    if (!isUndefined(obj.year)) {\n      for (var _i = 0, _orderedUnits = orderedUnits; _i < _orderedUnits.length; _i++) {\n        var u = _orderedUnits[_i];\n        if (isUndefined(obj[u])) {\n          obj[u] = defaultUnitValues[u];\n        }\n      }\n      var invalid = hasInvalidGregorianData(obj) || hasInvalidTimeData(obj);\n      if (invalid) {\n        return DateTime.invalid(invalid);\n      }\n      var offsetProvis = guessOffsetForZone(zone);\n      var _objToTS = objToTS(obj, offsetProvis, zone);\n      ts = _objToTS[0];\n      o = _objToTS[1];\n    } else {\n      ts = Settings.now();\n    }\n    return new DateTime({\n      ts: ts,\n      zone: zone,\n      loc: loc,\n      o: o\n    });\n  }\n  function diffRelative(start, end, opts) {\n    var round = isUndefined(opts.round) ? true : opts.round,\n      format = function format(c, unit) {\n        c = roundTo(c, round || opts.calendary ? 0 : 2, true);\n        var formatter = end.loc.clone(opts).relFormatter(opts);\n        return formatter.format(c, unit);\n      },\n      differ = function differ(unit) {\n        if (opts.calendary) {\n          if (!end.hasSame(start, unit)) {\n            return end.startOf(unit).diff(start.startOf(unit), unit).get(unit);\n          } else return 0;\n        } else {\n          return end.diff(start, unit).get(unit);\n        }\n      };\n    if (opts.unit) {\n      return format(differ(opts.unit), opts.unit);\n    }\n    for (var _iterator = _createForOfIteratorHelperLoose(opts.units), _step; !(_step = _iterator()).done;) {\n      var unit = _step.value;\n      var count = differ(unit);\n      if (Math.abs(count) >= 1) {\n        return format(count, unit);\n      }\n    }\n    return format(start > end ? -0 : 0, opts.units[opts.units.length - 1]);\n  }\n  function lastOpts(argList) {\n    var opts = {},\n      args;\n    if (argList.length > 0 && typeof argList[argList.length - 1] === \"object\") {\n      opts = argList[argList.length - 1];\n      args = Array.from(argList).slice(0, argList.length - 1);\n    } else {\n      args = Array.from(argList);\n    }\n    return [opts, args];\n  }\n\n  /**\n   * Timestamp to use for cached zone offset guesses (exposed for test)\n   */\n  var zoneOffsetTs;\n  /**\n   * Cache for zone offset guesses (exposed for test).\n   *\n   * This optimizes quickDT via guessOffsetForZone to avoid repeated calls of\n   * zone.offset().\n   */\n  var zoneOffsetGuessCache = {};\n\n  /**\n   * A DateTime is an immutable data structure representing a specific date and time and accompanying methods. It contains class and instance methods for creating, parsing, interrogating, transforming, and formatting them.\n   *\n   * A DateTime comprises of:\n   * * A timestamp. Each DateTime instance refers to a specific millisecond of the Unix epoch.\n   * * A time zone. Each instance is considered in the context of a specific zone (by default the local system's zone).\n   * * Configuration properties that effect how output strings are formatted, such as `locale`, `numberingSystem`, and `outputCalendar`.\n   *\n   * Here is a brief overview of the most commonly used functionality it provides:\n   *\n   * * **Creation**: To create a DateTime from its components, use one of its factory class methods: {@link DateTime.local}, {@link DateTime.utc}, and (most flexibly) {@link DateTime.fromObject}. To create one from a standard string format, use {@link DateTime.fromISO}, {@link DateTime.fromHTTP}, and {@link DateTime.fromRFC2822}. To create one from a custom string format, use {@link DateTime.fromFormat}. To create one from a native JS date, use {@link DateTime.fromJSDate}.\n   * * **Gregorian calendar and time**: To examine the Gregorian properties of a DateTime individually (i.e as opposed to collectively through {@link DateTime#toObject}), use the {@link DateTime#year}, {@link DateTime#month},\n   * {@link DateTime#day}, {@link DateTime#hour}, {@link DateTime#minute}, {@link DateTime#second}, {@link DateTime#millisecond} accessors.\n   * * **Week calendar**: For ISO week calendar attributes, see the {@link DateTime#weekYear}, {@link DateTime#weekNumber}, and {@link DateTime#weekday} accessors.\n   * * **Configuration** See the {@link DateTime#locale} and {@link DateTime#numberingSystem} accessors.\n   * * **Transformation**: To transform the DateTime into other DateTimes, use {@link DateTime#set}, {@link DateTime#reconfigure}, {@link DateTime#setZone}, {@link DateTime#setLocale}, {@link DateTime.plus}, {@link DateTime#minus}, {@link DateTime#endOf}, {@link DateTime#startOf}, {@link DateTime#toUTC}, and {@link DateTime#toLocal}.\n   * * **Output**: To convert the DateTime to other representations, use the {@link DateTime#toRelative}, {@link DateTime#toRelativeCalendar}, {@link DateTime#toJSON}, {@link DateTime#toISO}, {@link DateTime#toHTTP}, {@link DateTime#toObject}, {@link DateTime#toRFC2822}, {@link DateTime#toString}, {@link DateTime#toLocaleString}, {@link DateTime#toFormat}, {@link DateTime#toMillis} and {@link DateTime#toJSDate}.\n   *\n   * There's plenty others documented below. In addition, for more information on subtler topics like internationalization, time zones, alternative calendars, validity, and so on, see the external documentation.\n   */\n  var DateTime = /*#__PURE__*/function (_Symbol$for) {\n    /**\n     * @access private\n     */\n    function DateTime(config) {\n      var zone = config.zone || Settings.defaultZone;\n      var invalid = config.invalid || (Number.isNaN(config.ts) ? new Invalid(\"invalid input\") : null) || (!zone.isValid ? unsupportedZone(zone) : null);\n      /**\n       * @access private\n       */\n      this.ts = isUndefined(config.ts) ? Settings.now() : config.ts;\n      var c = null,\n        o = null;\n      if (!invalid) {\n        var unchanged = config.old && config.old.ts === this.ts && config.old.zone.equals(zone);\n        if (unchanged) {\n          var _ref = [config.old.c, config.old.o];\n          c = _ref[0];\n          o = _ref[1];\n        } else {\n          // If an offset has been passed and we have not been called from\n          // clone(), we can trust it and avoid the offset calculation.\n          var ot = isNumber(config.o) && !config.old ? config.o : zone.offset(this.ts);\n          c = tsToObj(this.ts, ot);\n          invalid = Number.isNaN(c.year) ? new Invalid(\"invalid input\") : null;\n          c = invalid ? null : c;\n          o = invalid ? null : ot;\n        }\n      }\n\n      /**\n       * @access private\n       */\n      this._zone = zone;\n      /**\n       * @access private\n       */\n      this.loc = config.loc || Locale.create();\n      /**\n       * @access private\n       */\n      this.invalid = invalid;\n      /**\n       * @access private\n       */\n      this.weekData = null;\n      /**\n       * @access private\n       */\n      this.localWeekData = null;\n      /**\n       * @access private\n       */\n      this.c = c;\n      /**\n       * @access private\n       */\n      this.o = o;\n      /**\n       * @access private\n       */\n      this.isLuxonDateTime = true;\n    }\n\n    // CONSTRUCT\n\n    /**\n     * Create a DateTime for the current instant, in the system's time zone.\n     *\n     * Use Settings to override these default values if needed.\n     * @example DateTime.now().toISO() //~> now in the ISO format\n     * @return {DateTime}\n     */\n    DateTime.now = function now() {\n      return new DateTime({});\n    }\n\n    /**\n     * Create a local DateTime\n     * @param {number} [year] - The calendar year. If omitted (as in, call `local()` with no arguments), the current time will be used\n     * @param {number} [month=1] - The month, 1-indexed\n     * @param {number} [day=1] - The day of the month, 1-indexed\n     * @param {number} [hour=0] - The hour of the day, in 24-hour time\n     * @param {number} [minute=0] - The minute of the hour, meaning a number between 0 and 59\n     * @param {number} [second=0] - The second of the minute, meaning a number between 0 and 59\n     * @param {number} [millisecond=0] - The millisecond of the second, meaning a number between 0 and 999\n     * @example DateTime.local()                                  //~> now\n     * @example DateTime.local({ zone: \"America/New_York\" })      //~> now, in US east coast time\n     * @example DateTime.local(2017)                              //~> 2017-01-01T00:00:00\n     * @example DateTime.local(2017, 3)                           //~> 2017-03-01T00:00:00\n     * @example DateTime.local(2017, 3, 12, { locale: \"fr\" })     //~> 2017-03-12T00:00:00, with a French locale\n     * @example DateTime.local(2017, 3, 12, 5)                    //~> 2017-03-12T05:00:00\n     * @example DateTime.local(2017, 3, 12, 5, { zone: \"utc\" })   //~> 2017-03-12T05:00:00, in UTC\n     * @example DateTime.local(2017, 3, 12, 5, 45)                //~> 2017-03-12T05:45:00\n     * @example DateTime.local(2017, 3, 12, 5, 45, 10)            //~> 2017-03-12T05:45:10\n     * @example DateTime.local(2017, 3, 12, 5, 45, 10, 765)       //~> 2017-03-12T05:45:10.765\n     * @return {DateTime}\n     */;\n    DateTime.local = function local() {\n      var _lastOpts = lastOpts(arguments),\n        opts = _lastOpts[0],\n        args = _lastOpts[1],\n        year = args[0],\n        month = args[1],\n        day = args[2],\n        hour = args[3],\n        minute = args[4],\n        second = args[5],\n        millisecond = args[6];\n      return quickDT({\n        year: year,\n        month: month,\n        day: day,\n        hour: hour,\n        minute: minute,\n        second: second,\n        millisecond: millisecond\n      }, opts);\n    }\n\n    /**\n     * Create a DateTime in UTC\n     * @param {number} [year] - The calendar year. If omitted (as in, call `utc()` with no arguments), the current time will be used\n     * @param {number} [month=1] - The month, 1-indexed\n     * @param {number} [day=1] - The day of the month\n     * @param {number} [hour=0] - The hour of the day, in 24-hour time\n     * @param {number} [minute=0] - The minute of the hour, meaning a number between 0 and 59\n     * @param {number} [second=0] - The second of the minute, meaning a number between 0 and 59\n     * @param {number} [millisecond=0] - The millisecond of the second, meaning a number between 0 and 999\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string} [options.locale] - a locale to set on the resulting DateTime instance\n     * @param {string} [options.outputCalendar] - the output calendar to set on the resulting DateTime instance\n     * @param {string} [options.numberingSystem] - the numbering system to set on the resulting DateTime instance\n     * @param {string} [options.weekSettings] - the week settings to set on the resulting DateTime instance\n     * @example DateTime.utc()                                              //~> now\n     * @example DateTime.utc(2017)                                          //~> 2017-01-01T00:00:00Z\n     * @example DateTime.utc(2017, 3)                                       //~> 2017-03-01T00:00:00Z\n     * @example DateTime.utc(2017, 3, 12)                                   //~> 2017-03-12T00:00:00Z\n     * @example DateTime.utc(2017, 3, 12, 5)                                //~> 2017-03-12T05:00:00Z\n     * @example DateTime.utc(2017, 3, 12, 5, 45)                            //~> 2017-03-12T05:45:00Z\n     * @example DateTime.utc(2017, 3, 12, 5, 45, { locale: \"fr\" })          //~> 2017-03-12T05:45:00Z with a French locale\n     * @example DateTime.utc(2017, 3, 12, 5, 45, 10)                        //~> 2017-03-12T05:45:10Z\n     * @example DateTime.utc(2017, 3, 12, 5, 45, 10, 765, { locale: \"fr\" }) //~> 2017-03-12T05:45:10.765Z with a French locale\n     * @return {DateTime}\n     */;\n    DateTime.utc = function utc() {\n      var _lastOpts2 = lastOpts(arguments),\n        opts = _lastOpts2[0],\n        args = _lastOpts2[1],\n        year = args[0],\n        month = args[1],\n        day = args[2],\n        hour = args[3],\n        minute = args[4],\n        second = args[5],\n        millisecond = args[6];\n      opts.zone = FixedOffsetZone.utcInstance;\n      return quickDT({\n        year: year,\n        month: month,\n        day: day,\n        hour: hour,\n        minute: minute,\n        second: second,\n        millisecond: millisecond\n      }, opts);\n    }\n\n    /**\n     * Create a DateTime from a JavaScript Date object. Uses the default zone.\n     * @param {Date} date - a JavaScript Date object\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into\n     * @return {DateTime}\n     */;\n    DateTime.fromJSDate = function fromJSDate(date, options) {\n      if (options === void 0) {\n        options = {};\n      }\n      var ts = isDate(date) ? date.valueOf() : NaN;\n      if (Number.isNaN(ts)) {\n        return DateTime.invalid(\"invalid input\");\n      }\n      var zoneToUse = normalizeZone(options.zone, Settings.defaultZone);\n      if (!zoneToUse.isValid) {\n        return DateTime.invalid(unsupportedZone(zoneToUse));\n      }\n      return new DateTime({\n        ts: ts,\n        zone: zoneToUse,\n        loc: Locale.fromObject(options)\n      });\n    }\n\n    /**\n     * Create a DateTime from a number of milliseconds since the epoch (meaning since 1 January 1970 00:00:00 UTC). Uses the default zone.\n     * @param {number} milliseconds - a number of milliseconds since 1970 UTC\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into\n     * @param {string} [options.locale] - a locale to set on the resulting DateTime instance\n     * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @param {string} options.weekSettings - the week settings to set on the resulting DateTime instance\n     * @return {DateTime}\n     */;\n    DateTime.fromMillis = function fromMillis(milliseconds, options) {\n      if (options === void 0) {\n        options = {};\n      }\n      if (!isNumber(milliseconds)) {\n        throw new InvalidArgumentError(\"fromMillis requires a numerical input, but received a \" + typeof milliseconds + \" with value \" + milliseconds);\n      } else if (milliseconds < -MAX_DATE || milliseconds > MAX_DATE) {\n        // this isn't perfect because we can still end up out of range because of additional shifting, but it's a start\n        return DateTime.invalid(\"Timestamp out of range\");\n      } else {\n        return new DateTime({\n          ts: milliseconds,\n          zone: normalizeZone(options.zone, Settings.defaultZone),\n          loc: Locale.fromObject(options)\n        });\n      }\n    }\n\n    /**\n     * Create a DateTime from a number of seconds since the epoch (meaning since 1 January 1970 00:00:00 UTC). Uses the default zone.\n     * @param {number} seconds - a number of seconds since 1970 UTC\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into\n     * @param {string} [options.locale] - a locale to set on the resulting DateTime instance\n     * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @param {string} options.weekSettings - the week settings to set on the resulting DateTime instance\n     * @return {DateTime}\n     */;\n    DateTime.fromSeconds = function fromSeconds(seconds, options) {\n      if (options === void 0) {\n        options = {};\n      }\n      if (!isNumber(seconds)) {\n        throw new InvalidArgumentError(\"fromSeconds requires a numerical input\");\n      } else {\n        return new DateTime({\n          ts: seconds * 1000,\n          zone: normalizeZone(options.zone, Settings.defaultZone),\n          loc: Locale.fromObject(options)\n        });\n      }\n    }\n\n    /**\n     * Create a DateTime from a JavaScript object with keys like 'year' and 'hour' with reasonable defaults.\n     * @param {Object} obj - the object to create the DateTime from\n     * @param {number} obj.year - a year, such as 1987\n     * @param {number} obj.month - a month, 1-12\n     * @param {number} obj.day - a day of the month, 1-31, depending on the month\n     * @param {number} obj.ordinal - day of the year, 1-365 or 366\n     * @param {number} obj.weekYear - an ISO week year\n     * @param {number} obj.weekNumber - an ISO week number, between 1 and 52 or 53, depending on the year\n     * @param {number} obj.weekday - an ISO weekday, 1-7, where 1 is Monday and 7 is Sunday\n     * @param {number} obj.localWeekYear - a week year, according to the locale\n     * @param {number} obj.localWeekNumber - a week number, between 1 and 52 or 53, depending on the year, according to the locale\n     * @param {number} obj.localWeekday - a weekday, 1-7, where 1 is the first and 7 is the last day of the week, according to the locale\n     * @param {number} obj.hour - hour of the day, 0-23\n     * @param {number} obj.minute - minute of the hour, 0-59\n     * @param {number} obj.second - second of the minute, 0-59\n     * @param {number} obj.millisecond - millisecond of the second, 0-999\n     * @param {Object} opts - options for creating this DateTime\n     * @param {string|Zone} [opts.zone='local'] - interpret the numbers in the context of a particular zone. Can take any value taken as the first argument to setZone()\n     * @param {string} [opts.locale='system\\'s locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance\n     * @example DateTime.fromObject({ year: 1982, month: 5, day: 25}).toISODate() //=> '1982-05-25'\n     * @example DateTime.fromObject({ year: 1982 }).toISODate() //=> '1982-01-01'\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6 }) //~> today at 10:26:06\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6 }, { zone: 'utc' }),\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6 }, { zone: 'local' })\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6 }, { zone: 'America/New_York' })\n     * @example DateTime.fromObject({ weekYear: 2016, weekNumber: 2, weekday: 3 }).toISODate() //=> '2016-01-13'\n     * @example DateTime.fromObject({ localWeekYear: 2022, localWeekNumber: 1, localWeekday: 1 }, { locale: \"en-US\" }).toISODate() //=> '2021-12-26'\n     * @return {DateTime}\n     */;\n    DateTime.fromObject = function fromObject(obj, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      obj = obj || {};\n      var zoneToUse = normalizeZone(opts.zone, Settings.defaultZone);\n      if (!zoneToUse.isValid) {\n        return DateTime.invalid(unsupportedZone(zoneToUse));\n      }\n      var loc = Locale.fromObject(opts);\n      var normalized = normalizeObject(obj, normalizeUnitWithLocalWeeks);\n      var _usesLocalWeekValues = usesLocalWeekValues(normalized, loc),\n        minDaysInFirstWeek = _usesLocalWeekValues.minDaysInFirstWeek,\n        startOfWeek = _usesLocalWeekValues.startOfWeek;\n      var tsNow = Settings.now(),\n        offsetProvis = !isUndefined(opts.specificOffset) ? opts.specificOffset : zoneToUse.offset(tsNow),\n        containsOrdinal = !isUndefined(normalized.ordinal),\n        containsGregorYear = !isUndefined(normalized.year),\n        containsGregorMD = !isUndefined(normalized.month) || !isUndefined(normalized.day),\n        containsGregor = containsGregorYear || containsGregorMD,\n        definiteWeekDef = normalized.weekYear || normalized.weekNumber;\n\n      // cases:\n      // just a weekday -> this week's instance of that weekday, no worries\n      // (gregorian data or ordinal) + (weekYear or weekNumber) -> error\n      // (gregorian month or day) + ordinal -> error\n      // otherwise just use weeks or ordinals or gregorian, depending on what's specified\n\n      if ((containsGregor || containsOrdinal) && definiteWeekDef) {\n        throw new ConflictingSpecificationError(\"Can't mix weekYear/weekNumber units with year/month/day or ordinals\");\n      }\n      if (containsGregorMD && containsOrdinal) {\n        throw new ConflictingSpecificationError(\"Can't mix ordinal dates with month/day\");\n      }\n      var useWeekData = definiteWeekDef || normalized.weekday && !containsGregor;\n\n      // configure ourselves to deal with gregorian dates or week stuff\n      var units,\n        defaultValues,\n        objNow = tsToObj(tsNow, offsetProvis);\n      if (useWeekData) {\n        units = orderedWeekUnits;\n        defaultValues = defaultWeekUnitValues;\n        objNow = gregorianToWeek(objNow, minDaysInFirstWeek, startOfWeek);\n      } else if (containsOrdinal) {\n        units = orderedOrdinalUnits;\n        defaultValues = defaultOrdinalUnitValues;\n        objNow = gregorianToOrdinal(objNow);\n      } else {\n        units = orderedUnits;\n        defaultValues = defaultUnitValues;\n      }\n\n      // set default values for missing stuff\n      var foundFirst = false;\n      for (var _iterator2 = _createForOfIteratorHelperLoose(units), _step2; !(_step2 = _iterator2()).done;) {\n        var u = _step2.value;\n        var v = normalized[u];\n        if (!isUndefined(v)) {\n          foundFirst = true;\n        } else if (foundFirst) {\n          normalized[u] = defaultValues[u];\n        } else {\n          normalized[u] = objNow[u];\n        }\n      }\n\n      // make sure the values we have are in range\n      var higherOrderInvalid = useWeekData ? hasInvalidWeekData(normalized, minDaysInFirstWeek, startOfWeek) : containsOrdinal ? hasInvalidOrdinalData(normalized) : hasInvalidGregorianData(normalized),\n        invalid = higherOrderInvalid || hasInvalidTimeData(normalized);\n      if (invalid) {\n        return DateTime.invalid(invalid);\n      }\n\n      // compute the actual time\n      var gregorian = useWeekData ? weekToGregorian(normalized, minDaysInFirstWeek, startOfWeek) : containsOrdinal ? ordinalToGregorian(normalized) : normalized,\n        _objToTS2 = objToTS(gregorian, offsetProvis, zoneToUse),\n        tsFinal = _objToTS2[0],\n        offsetFinal = _objToTS2[1],\n        inst = new DateTime({\n          ts: tsFinal,\n          zone: zoneToUse,\n          o: offsetFinal,\n          loc: loc\n        });\n\n      // gregorian data + weekday serves only to validate\n      if (normalized.weekday && containsGregor && obj.weekday !== inst.weekday) {\n        return DateTime.invalid(\"mismatched weekday\", \"you can't specify both a weekday of \" + normalized.weekday + \" and a date of \" + inst.toISO());\n      }\n      if (!inst.isValid) {\n        return DateTime.invalid(inst.invalid);\n      }\n      return inst;\n    }\n\n    /**\n     * Create a DateTime from an ISO 8601 string\n     * @param {string} text - the ISO string\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the time to this zone\n     * @param {boolean} [opts.setZone=false] - override the zone with a fixed-offset zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} [opts.outputCalendar] - the output calendar to set on the resulting DateTime instance\n     * @param {string} [opts.numberingSystem] - the numbering system to set on the resulting DateTime instance\n     * @param {string} [opts.weekSettings] - the week settings to set on the resulting DateTime instance\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123')\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00')\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00', {setZone: true})\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123', {zone: 'utc'})\n     * @example DateTime.fromISO('2016-W05-4')\n     * @return {DateTime}\n     */;\n    DateTime.fromISO = function fromISO(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      var _parseISODate = parseISODate(text),\n        vals = _parseISODate[0],\n        parsedZone = _parseISODate[1];\n      return parseDataToDateTime(vals, parsedZone, opts, \"ISO 8601\", text);\n    }\n\n    /**\n     * Create a DateTime from an RFC 2822 string\n     * @param {string} text - the RFC 2822 string\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - convert the time to this zone. Since the offset is always specified in the string itself, this has no effect on the interpretation of string, merely the zone the resulting DateTime is expressed in.\n     * @param {boolean} [opts.setZone=false] - override the zone with a fixed-offset zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance\n     * @example DateTime.fromRFC2822('25 Nov 2016 13:23:12 GMT')\n     * @example DateTime.fromRFC2822('Fri, 25 Nov 2016 13:23:12 +0600')\n     * @example DateTime.fromRFC2822('25 Nov 2016 13:23 Z')\n     * @return {DateTime}\n     */;\n    DateTime.fromRFC2822 = function fromRFC2822(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      var _parseRFC2822Date = parseRFC2822Date(text),\n        vals = _parseRFC2822Date[0],\n        parsedZone = _parseRFC2822Date[1];\n      return parseDataToDateTime(vals, parsedZone, opts, \"RFC 2822\", text);\n    }\n\n    /**\n     * Create a DateTime from an HTTP header date\n     * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1\n     * @param {string} text - the HTTP header date\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - convert the time to this zone. Since HTTP dates are always in UTC, this has no effect on the interpretation of string, merely the zone the resulting DateTime is expressed in.\n     * @param {boolean} [opts.setZone=false] - override the zone with the fixed-offset zone specified in the string. For HTTP dates, this is always UTC, so this option is equivalent to setting the `zone` option to 'utc', but this option is included for consistency with similar methods.\n     * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance\n     * @example DateTime.fromHTTP('Sun, 06 Nov 1994 08:49:37 GMT')\n     * @example DateTime.fromHTTP('Sunday, 06-Nov-94 08:49:37 GMT')\n     * @example DateTime.fromHTTP('Sun Nov  6 08:49:37 1994')\n     * @return {DateTime}\n     */;\n    DateTime.fromHTTP = function fromHTTP(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      var _parseHTTPDate = parseHTTPDate(text),\n        vals = _parseHTTPDate[0],\n        parsedZone = _parseHTTPDate[1];\n      return parseDataToDateTime(vals, parsedZone, opts, \"HTTP\", opts);\n    }\n\n    /**\n     * Create a DateTime from an input string and format string.\n     * Defaults to en-US if no locale has been specified, regardless of the system's locale. For a table of tokens and their interpretations, see [here](https://moment.github.io/luxon/#/parsing?id=table-of-tokens).\n     * @param {string} text - the string to parse\n     * @param {string} fmt - the format the string is expected to be in (see the link below for the formats)\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the DateTime to this zone\n     * @param {boolean} [opts.setZone=false] - override the zone with a zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='en-US'] - a locale string to use when parsing. Will also set the DateTime to this locale\n     * @param {string} opts.numberingSystem - the numbering system to use when parsing. Will also set the resulting DateTime to this numbering system\n     * @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @return {DateTime}\n     */;\n    DateTime.fromFormat = function fromFormat(text, fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (isUndefined(text) || isUndefined(fmt)) {\n        throw new InvalidArgumentError(\"fromFormat requires an input string and a format\");\n      }\n      var _opts = opts,\n        _opts$locale = _opts.locale,\n        locale = _opts$locale === void 0 ? null : _opts$locale,\n        _opts$numberingSystem = _opts.numberingSystem,\n        numberingSystem = _opts$numberingSystem === void 0 ? null : _opts$numberingSystem,\n        localeToUse = Locale.fromOpts({\n          locale: locale,\n          numberingSystem: numberingSystem,\n          defaultToEN: true\n        }),\n        _parseFromTokens = parseFromTokens(localeToUse, text, fmt),\n        vals = _parseFromTokens[0],\n        parsedZone = _parseFromTokens[1],\n        specificOffset = _parseFromTokens[2],\n        invalid = _parseFromTokens[3];\n      if (invalid) {\n        return DateTime.invalid(invalid);\n      } else {\n        return parseDataToDateTime(vals, parsedZone, opts, \"format \" + fmt, text, specificOffset);\n      }\n    }\n\n    /**\n     * @deprecated use fromFormat instead\n     */;\n    DateTime.fromString = function fromString(text, fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return DateTime.fromFormat(text, fmt, opts);\n    }\n\n    /**\n     * Create a DateTime from a SQL date, time, or datetime\n     * Defaults to en-US if no locale has been specified, regardless of the system's locale\n     * @param {string} text - the string to parse\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the DateTime to this zone\n     * @param {boolean} [opts.setZone=false] - override the zone with a zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='en-US'] - a locale string to use when parsing. Will also set the DateTime to this locale\n     * @param {string} opts.numberingSystem - the numbering system to use when parsing. Will also set the resulting DateTime to this numbering system\n     * @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @example DateTime.fromSQL('2017-05-15')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342+06:00')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342 America/Los_Angeles')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342 America/Los_Angeles', { setZone: true })\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342', { zone: 'America/Los_Angeles' })\n     * @example DateTime.fromSQL('09:12:34.342')\n     * @return {DateTime}\n     */;\n    DateTime.fromSQL = function fromSQL(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      var _parseSQL = parseSQL(text),\n        vals = _parseSQL[0],\n        parsedZone = _parseSQL[1];\n      return parseDataToDateTime(vals, parsedZone, opts, \"SQL\", text);\n    }\n\n    /**\n     * Create an invalid DateTime.\n     * @param {string} reason - simple string of why this DateTime is invalid. Should not contain parameters or anything else data-dependent.\n     * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information\n     * @return {DateTime}\n     */;\n    DateTime.invalid = function invalid(reason, explanation) {\n      if (explanation === void 0) {\n        explanation = null;\n      }\n      if (!reason) {\n        throw new InvalidArgumentError(\"need to specify a reason the DateTime is invalid\");\n      }\n      var invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);\n      if (Settings.throwOnInvalid) {\n        throw new InvalidDateTimeError(invalid);\n      } else {\n        return new DateTime({\n          invalid: invalid\n        });\n      }\n    }\n\n    /**\n     * Check if an object is an instance of DateTime. Works across context boundaries\n     * @param {object} o\n     * @return {boolean}\n     */;\n    DateTime.isDateTime = function isDateTime(o) {\n      return o && o.isLuxonDateTime || false;\n    }\n\n    /**\n     * Produce the format string for a set of options\n     * @param formatOpts\n     * @param localeOpts\n     * @returns {string}\n     */;\n    DateTime.parseFormatForOpts = function parseFormatForOpts(formatOpts, localeOpts) {\n      if (localeOpts === void 0) {\n        localeOpts = {};\n      }\n      var tokenList = formatOptsToTokens(formatOpts, Locale.fromObject(localeOpts));\n      return !tokenList ? null : tokenList.map(function (t) {\n        return t ? t.val : null;\n      }).join(\"\");\n    }\n\n    /**\n     * Produce the the fully expanded format token for the locale\n     * Does NOT quote characters, so quoted tokens will not round trip correctly\n     * @param fmt\n     * @param localeOpts\n     * @returns {string}\n     */;\n    DateTime.expandFormat = function expandFormat(fmt, localeOpts) {\n      if (localeOpts === void 0) {\n        localeOpts = {};\n      }\n      var expanded = expandMacroTokens(Formatter.parseFormat(fmt), Locale.fromObject(localeOpts));\n      return expanded.map(function (t) {\n        return t.val;\n      }).join(\"\");\n    };\n    DateTime.resetCache = function resetCache() {\n      zoneOffsetTs = undefined;\n      zoneOffsetGuessCache = {};\n    }\n\n    // INFO\n\n    /**\n     * Get the value of unit.\n     * @param {string} unit - a unit such as 'minute' or 'day'\n     * @example DateTime.local(2017, 7, 4).get('month'); //=> 7\n     * @example DateTime.local(2017, 7, 4).get('day'); //=> 4\n     * @return {number}\n     */;\n    var _proto = DateTime.prototype;\n    _proto.get = function get(unit) {\n      return this[unit];\n    }\n\n    /**\n     * Returns whether the DateTime is valid. Invalid DateTimes occur when:\n     * * The DateTime was created from invalid calendar information, such as the 13th month or February 30\n     * * The DateTime was created by an operation on another invalid date\n     * @type {boolean}\n     */;\n    /**\n     * Get those DateTimes which have the same local time as this DateTime, but a different offset from UTC\n     * in this DateTime's zone. During DST changes local time can be ambiguous, for example\n     * `2023-10-29T02:30:00` in `Europe/Berlin` can have offset `+01:00` or `+02:00`.\n     * This method will return both possible DateTimes if this DateTime's local time is ambiguous.\n     * @returns {DateTime[]}\n     */\n    _proto.getPossibleOffsets = function getPossibleOffsets() {\n      if (!this.isValid || this.isOffsetFixed) {\n        return [this];\n      }\n      var dayMs = 86400000;\n      var minuteMs = 60000;\n      var localTS = objToLocalTS(this.c);\n      var oEarlier = this.zone.offset(localTS - dayMs);\n      var oLater = this.zone.offset(localTS + dayMs);\n      var o1 = this.zone.offset(localTS - oEarlier * minuteMs);\n      var o2 = this.zone.offset(localTS - oLater * minuteMs);\n      if (o1 === o2) {\n        return [this];\n      }\n      var ts1 = localTS - o1 * minuteMs;\n      var ts2 = localTS - o2 * minuteMs;\n      var c1 = tsToObj(ts1, o1);\n      var c2 = tsToObj(ts2, o2);\n      if (c1.hour === c2.hour && c1.minute === c2.minute && c1.second === c2.second && c1.millisecond === c2.millisecond) {\n        return [clone(this, {\n          ts: ts1\n        }), clone(this, {\n          ts: ts2\n        })];\n      }\n      return [this];\n    }\n\n    /**\n     * Returns true if this DateTime is in a leap year, false otherwise\n     * @example DateTime.local(2016).isInLeapYear //=> true\n     * @example DateTime.local(2013).isInLeapYear //=> false\n     * @type {boolean}\n     */;\n    /**\n     * Returns the resolved Intl options for this DateTime.\n     * This is useful in understanding the behavior of formatting methods\n     * @param {Object} opts - the same options as toLocaleString\n     * @return {Object}\n     */\n    _proto.resolvedLocaleOptions = function resolvedLocaleOptions(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      var _Formatter$create$res = Formatter.create(this.loc.clone(opts), opts).resolvedOptions(this),\n        locale = _Formatter$create$res.locale,\n        numberingSystem = _Formatter$create$res.numberingSystem,\n        calendar = _Formatter$create$res.calendar;\n      return {\n        locale: locale,\n        numberingSystem: numberingSystem,\n        outputCalendar: calendar\n      };\n    }\n\n    // TRANSFORM\n\n    /**\n     * \"Set\" the DateTime's zone to UTC. Returns a newly-constructed DateTime.\n     *\n     * Equivalent to {@link DateTime#setZone}('utc')\n     * @param {number} [offset=0] - optionally, an offset from UTC in minutes\n     * @param {Object} [opts={}] - options to pass to `setZone()`\n     * @return {DateTime}\n     */;\n    _proto.toUTC = function toUTC(offset, opts) {\n      if (offset === void 0) {\n        offset = 0;\n      }\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.setZone(FixedOffsetZone.instance(offset), opts);\n    }\n\n    /**\n     * \"Set\" the DateTime's zone to the host's local zone. Returns a newly-constructed DateTime.\n     *\n     * Equivalent to `setZone('local')`\n     * @return {DateTime}\n     */;\n    _proto.toLocal = function toLocal() {\n      return this.setZone(Settings.defaultZone);\n    }\n\n    /**\n     * \"Set\" the DateTime's zone to specified zone. Returns a newly-constructed DateTime.\n     *\n     * By default, the setter keeps the underlying time the same (as in, the same timestamp), but the new instance will report different local times and consider DSTs when making computations, as with {@link DateTime#plus}. You may wish to use {@link DateTime#toLocal} and {@link DateTime#toUTC} which provide simple convenience wrappers for commonly used zones.\n     * @param {string|Zone} [zone='local'] - a zone identifier. As a string, that can be any IANA zone supported by the host environment, or a fixed-offset name of the form 'UTC+3', or the strings 'local' or 'utc'. You may also supply an instance of a {@link DateTime#Zone} class.\n     * @param {Object} opts - options\n     * @param {boolean} [opts.keepLocalTime=false] - If true, adjust the underlying time so that the local time stays the same, but in the target zone. You should rarely need this.\n     * @return {DateTime}\n     */;\n    _proto.setZone = function setZone(zone, _temp) {\n      var _ref2 = _temp === void 0 ? {} : _temp,\n        _ref2$keepLocalTime = _ref2.keepLocalTime,\n        keepLocalTime = _ref2$keepLocalTime === void 0 ? false : _ref2$keepLocalTime,\n        _ref2$keepCalendarTim = _ref2.keepCalendarTime,\n        keepCalendarTime = _ref2$keepCalendarTim === void 0 ? false : _ref2$keepCalendarTim;\n      zone = normalizeZone(zone, Settings.defaultZone);\n      if (zone.equals(this.zone)) {\n        return this;\n      } else if (!zone.isValid) {\n        return DateTime.invalid(unsupportedZone(zone));\n      } else {\n        var newTS = this.ts;\n        if (keepLocalTime || keepCalendarTime) {\n          var offsetGuess = zone.offset(this.ts);\n          var asObj = this.toObject();\n          var _objToTS3 = objToTS(asObj, offsetGuess, zone);\n          newTS = _objToTS3[0];\n        }\n        return clone(this, {\n          ts: newTS,\n          zone: zone\n        });\n      }\n    }\n\n    /**\n     * \"Set\" the locale, numberingSystem, or outputCalendar. Returns a newly-constructed DateTime.\n     * @param {Object} properties - the properties to set\n     * @example DateTime.local(2017, 5, 25).reconfigure({ locale: 'en-GB' })\n     * @return {DateTime}\n     */;\n    _proto.reconfigure = function reconfigure(_temp2) {\n      var _ref3 = _temp2 === void 0 ? {} : _temp2,\n        locale = _ref3.locale,\n        numberingSystem = _ref3.numberingSystem,\n        outputCalendar = _ref3.outputCalendar;\n      var loc = this.loc.clone({\n        locale: locale,\n        numberingSystem: numberingSystem,\n        outputCalendar: outputCalendar\n      });\n      return clone(this, {\n        loc: loc\n      });\n    }\n\n    /**\n     * \"Set\" the locale. Returns a newly-constructed DateTime.\n     * Just a convenient alias for reconfigure({ locale })\n     * @example DateTime.local(2017, 5, 25).setLocale('en-GB')\n     * @return {DateTime}\n     */;\n    _proto.setLocale = function setLocale(locale) {\n      return this.reconfigure({\n        locale: locale\n      });\n    }\n\n    /**\n     * \"Set\" the values of specified units. Returns a newly-constructed DateTime.\n     * You can only set units with this method; for \"setting\" metadata, see {@link DateTime#reconfigure} and {@link DateTime#setZone}.\n     *\n     * This method also supports setting locale-based week units, i.e. `localWeekday`, `localWeekNumber` and `localWeekYear`.\n     * They cannot be mixed with ISO-week units like `weekday`.\n     * @param {Object} values - a mapping of units to numbers\n     * @example dt.set({ year: 2017 })\n     * @example dt.set({ hour: 8, minute: 30 })\n     * @example dt.set({ weekday: 5 })\n     * @example dt.set({ year: 2005, ordinal: 234 })\n     * @return {DateTime}\n     */;\n    _proto.set = function set(values) {\n      if (!this.isValid) return this;\n      var normalized = normalizeObject(values, normalizeUnitWithLocalWeeks);\n      var _usesLocalWeekValues2 = usesLocalWeekValues(normalized, this.loc),\n        minDaysInFirstWeek = _usesLocalWeekValues2.minDaysInFirstWeek,\n        startOfWeek = _usesLocalWeekValues2.startOfWeek;\n      var settingWeekStuff = !isUndefined(normalized.weekYear) || !isUndefined(normalized.weekNumber) || !isUndefined(normalized.weekday),\n        containsOrdinal = !isUndefined(normalized.ordinal),\n        containsGregorYear = !isUndefined(normalized.year),\n        containsGregorMD = !isUndefined(normalized.month) || !isUndefined(normalized.day),\n        containsGregor = containsGregorYear || containsGregorMD,\n        definiteWeekDef = normalized.weekYear || normalized.weekNumber;\n      if ((containsGregor || containsOrdinal) && definiteWeekDef) {\n        throw new ConflictingSpecificationError(\"Can't mix weekYear/weekNumber units with year/month/day or ordinals\");\n      }\n      if (containsGregorMD && containsOrdinal) {\n        throw new ConflictingSpecificationError(\"Can't mix ordinal dates with month/day\");\n      }\n      var mixed;\n      if (settingWeekStuff) {\n        mixed = weekToGregorian(_extends({}, gregorianToWeek(this.c, minDaysInFirstWeek, startOfWeek), normalized), minDaysInFirstWeek, startOfWeek);\n      } else if (!isUndefined(normalized.ordinal)) {\n        mixed = ordinalToGregorian(_extends({}, gregorianToOrdinal(this.c), normalized));\n      } else {\n        mixed = _extends({}, this.toObject(), normalized);\n\n        // if we didn't set the day but we ended up on an overflow date,\n        // use the last day of the right month\n        if (isUndefined(normalized.day)) {\n          mixed.day = Math.min(daysInMonth(mixed.year, mixed.month), mixed.day);\n        }\n      }\n      var _objToTS4 = objToTS(mixed, this.o, this.zone),\n        ts = _objToTS4[0],\n        o = _objToTS4[1];\n      return clone(this, {\n        ts: ts,\n        o: o\n      });\n    }\n\n    /**\n     * Add a period of time to this DateTime and return the resulting DateTime\n     *\n     * Adding hours, minutes, seconds, or milliseconds increases the timestamp by the right number of milliseconds. Adding days, months, or years shifts the calendar, accounting for DSTs and leap years along the way. Thus, `dt.plus({ hours: 24 })` may result in a different time than `dt.plus({ days: 1 })` if there's a DST shift in between.\n     * @param {Duration|Object|number} duration - The amount to add. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     * @example DateTime.now().plus(123) //~> in 123 milliseconds\n     * @example DateTime.now().plus({ minutes: 15 }) //~> in 15 minutes\n     * @example DateTime.now().plus({ days: 1 }) //~> this time tomorrow\n     * @example DateTime.now().plus({ days: -1 }) //~> this time yesterday\n     * @example DateTime.now().plus({ hours: 3, minutes: 13 }) //~> in 3 hr, 13 min\n     * @example DateTime.now().plus(Duration.fromObject({ hours: 3, minutes: 13 })) //~> in 3 hr, 13 min\n     * @return {DateTime}\n     */;\n    _proto.plus = function plus(duration) {\n      if (!this.isValid) return this;\n      var dur = Duration.fromDurationLike(duration);\n      return clone(this, adjustTime(this, dur));\n    }\n\n    /**\n     * Subtract a period of time to this DateTime and return the resulting DateTime\n     * See {@link DateTime#plus}\n     * @param {Duration|Object|number} duration - The amount to subtract. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     @return {DateTime}\n     */;\n    _proto.minus = function minus(duration) {\n      if (!this.isValid) return this;\n      var dur = Duration.fromDurationLike(duration).negate();\n      return clone(this, adjustTime(this, dur));\n    }\n\n    /**\n     * \"Set\" this DateTime to the beginning of a unit of time.\n     * @param {string} unit - The unit to go to the beginning of. Can be 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', or 'millisecond'.\n     * @param {Object} opts - options\n     * @param {boolean} [opts.useLocaleWeeks=false] - If true, use weeks based on the locale, i.e. use the locale-dependent start of the week\n     * @example DateTime.local(2014, 3, 3).startOf('month').toISODate(); //=> '2014-03-01'\n     * @example DateTime.local(2014, 3, 3).startOf('year').toISODate(); //=> '2014-01-01'\n     * @example DateTime.local(2014, 3, 3).startOf('week').toISODate(); //=> '2014-03-03', weeks always start on Mondays\n     * @example DateTime.local(2014, 3, 3, 5, 30).startOf('day').toISOTime(); //=> '00:00.000-05:00'\n     * @example DateTime.local(2014, 3, 3, 5, 30).startOf('hour').toISOTime(); //=> '05:00:00.000-05:00'\n     * @return {DateTime}\n     */;\n    _proto.startOf = function startOf(unit, _temp3) {\n      var _ref4 = _temp3 === void 0 ? {} : _temp3,\n        _ref4$useLocaleWeeks = _ref4.useLocaleWeeks,\n        useLocaleWeeks = _ref4$useLocaleWeeks === void 0 ? false : _ref4$useLocaleWeeks;\n      if (!this.isValid) return this;\n      var o = {},\n        normalizedUnit = Duration.normalizeUnit(unit);\n      switch (normalizedUnit) {\n        case \"years\":\n          o.month = 1;\n        // falls through\n        case \"quarters\":\n        case \"months\":\n          o.day = 1;\n        // falls through\n        case \"weeks\":\n        case \"days\":\n          o.hour = 0;\n        // falls through\n        case \"hours\":\n          o.minute = 0;\n        // falls through\n        case \"minutes\":\n          o.second = 0;\n        // falls through\n        case \"seconds\":\n          o.millisecond = 0;\n          break;\n        // no default, invalid units throw in normalizeUnit()\n      }\n\n      if (normalizedUnit === \"weeks\") {\n        if (useLocaleWeeks) {\n          var startOfWeek = this.loc.getStartOfWeek();\n          var weekday = this.weekday;\n          if (weekday < startOfWeek) {\n            o.weekNumber = this.weekNumber - 1;\n          }\n          o.weekday = startOfWeek;\n        } else {\n          o.weekday = 1;\n        }\n      }\n      if (normalizedUnit === \"quarters\") {\n        var q = Math.ceil(this.month / 3);\n        o.month = (q - 1) * 3 + 1;\n      }\n      return this.set(o);\n    }\n\n    /**\n     * \"Set\" this DateTime to the end (meaning the last millisecond) of a unit of time\n     * @param {string} unit - The unit to go to the end of. Can be 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', or 'millisecond'.\n     * @param {Object} opts - options\n     * @param {boolean} [opts.useLocaleWeeks=false] - If true, use weeks based on the locale, i.e. use the locale-dependent start of the week\n     * @example DateTime.local(2014, 3, 3).endOf('month').toISO(); //=> '2014-03-31T23:59:59.999-05:00'\n     * @example DateTime.local(2014, 3, 3).endOf('year').toISO(); //=> '2014-12-31T23:59:59.999-05:00'\n     * @example DateTime.local(2014, 3, 3).endOf('week').toISO(); // => '2014-03-09T23:59:59.999-05:00', weeks start on Mondays\n     * @example DateTime.local(2014, 3, 3, 5, 30).endOf('day').toISO(); //=> '2014-03-03T23:59:59.999-05:00'\n     * @example DateTime.local(2014, 3, 3, 5, 30).endOf('hour').toISO(); //=> '2014-03-03T05:59:59.999-05:00'\n     * @return {DateTime}\n     */;\n    _proto.endOf = function endOf(unit, opts) {\n      var _this$plus;\n      return this.isValid ? this.plus((_this$plus = {}, _this$plus[unit] = 1, _this$plus)).startOf(unit, opts).minus(1) : this;\n    }\n\n    // OUTPUT\n\n    /**\n     * Returns a string representation of this DateTime formatted according to the specified format string.\n     * **You may not want this.** See {@link DateTime#toLocaleString} for a more flexible formatting tool. For a table of tokens and their interpretations, see [here](https://moment.github.io/luxon/#/formatting?id=table-of-tokens).\n     * Defaults to en-US if no locale has been specified, regardless of the system's locale.\n     * @param {string} fmt - the format string\n     * @param {Object} opts - opts to override the configuration options on this DateTime\n     * @example DateTime.now().toFormat('yyyy LLL dd') //=> '2017 Apr 22'\n     * @example DateTime.now().setLocale('fr').toFormat('yyyy LLL dd') //=> '2017 avr. 22'\n     * @example DateTime.now().toFormat('yyyy LLL dd', { locale: \"fr\" }) //=> '2017 avr. 22'\n     * @example DateTime.now().toFormat(\"HH 'hours and' mm 'minutes'\") //=> '20 hours and 55 minutes'\n     * @return {string}\n     */;\n    _proto.toFormat = function toFormat(fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.isValid ? Formatter.create(this.loc.redefaultToEN(opts)).formatDateTimeFromString(this, fmt) : INVALID;\n    }\n\n    /**\n     * Returns a localized string representing this date. Accepts the same options as the Intl.DateTimeFormat constructor and any presets defined by Luxon, such as `DateTime.DATE_FULL` or `DateTime.TIME_SIMPLE`.\n     * The exact behavior of this method is browser-specific, but in general it will return an appropriate representation\n     * of the DateTime in the assigned locale.\n     * Defaults to the system's locale if no locale has been specified\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param formatOpts {Object} - Intl.DateTimeFormat constructor options and configuration options\n     * @param {Object} opts - opts to override the configuration options on this DateTime\n     * @example DateTime.now().toLocaleString(); //=> 4/20/2017\n     * @example DateTime.now().setLocale('en-gb').toLocaleString(); //=> '20/04/2017'\n     * @example DateTime.now().toLocaleString(DateTime.DATE_FULL); //=> 'April 20, 2017'\n     * @example DateTime.now().toLocaleString(DateTime.DATE_FULL, { locale: 'fr' }); //=> '28 ao\u00fbt 2022'\n     * @example DateTime.now().toLocaleString(DateTime.TIME_SIMPLE); //=> '11:32 AM'\n     * @example DateTime.now().toLocaleString(DateTime.DATETIME_SHORT); //=> '4/20/2017, 11:32 AM'\n     * @example DateTime.now().toLocaleString({ weekday: 'long', month: 'long', day: '2-digit' }); //=> 'Thursday, April 20'\n     * @example DateTime.now().toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> 'Thu, Apr 20, 11:27 AM'\n     * @example DateTime.now().toLocaleString({ hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }); //=> '11:32'\n     * @return {string}\n     */;\n    _proto.toLocaleString = function toLocaleString(formatOpts, opts) {\n      if (formatOpts === void 0) {\n        formatOpts = DATE_SHORT;\n      }\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.isValid ? Formatter.create(this.loc.clone(opts), formatOpts).formatDateTime(this) : INVALID;\n    }\n\n    /**\n     * Returns an array of format \"parts\", meaning individual tokens along with metadata. This is allows callers to post-process individual sections of the formatted output.\n     * Defaults to the system's locale if no locale has been specified\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/formatToParts\n     * @param opts {Object} - Intl.DateTimeFormat constructor options, same as `toLocaleString`.\n     * @example DateTime.now().toLocaleParts(); //=> [\n     *                                   //=>   { type: 'day', value: '25' },\n     *                                   //=>   { type: 'literal', value: '/' },\n     *                                   //=>   { type: 'month', value: '05' },\n     *                                   //=>   { type: 'literal', value: '/' },\n     *                                   //=>   { type: 'year', value: '1982' }\n     *                                   //=> ]\n     */;\n    _proto.toLocaleParts = function toLocaleParts(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.isValid ? Formatter.create(this.loc.clone(opts), opts).formatDateTimeParts(this) : [];\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime\n     * @param {Object} opts - options\n     * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0\n     * @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @param {boolean} [opts.extendedZone=false] - add the time zone format extension\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example DateTime.utc(1983, 5, 25).toISO() //=> '1982-05-25T00:00:00.000Z'\n     * @example DateTime.now().toISO() //=> '2017-04-22T20:47:05.335-04:00'\n     * @example DateTime.now().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335'\n     * @example DateTime.now().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400'\n     * @return {string}\n     */;\n    _proto.toISO = function toISO(_temp4) {\n      var _ref5 = _temp4 === void 0 ? {} : _temp4,\n        _ref5$format = _ref5.format,\n        format = _ref5$format === void 0 ? \"extended\" : _ref5$format,\n        _ref5$suppressSeconds = _ref5.suppressSeconds,\n        suppressSeconds = _ref5$suppressSeconds === void 0 ? false : _ref5$suppressSeconds,\n        _ref5$suppressMillise = _ref5.suppressMilliseconds,\n        suppressMilliseconds = _ref5$suppressMillise === void 0 ? false : _ref5$suppressMillise,\n        _ref5$includeOffset = _ref5.includeOffset,\n        includeOffset = _ref5$includeOffset === void 0 ? true : _ref5$includeOffset,\n        _ref5$extendedZone = _ref5.extendedZone,\n        extendedZone = _ref5$extendedZone === void 0 ? false : _ref5$extendedZone;\n      if (!this.isValid) {\n        return null;\n      }\n      var ext = format === \"extended\";\n      var c = _toISODate(this, ext);\n      c += \"T\";\n      c += _toISOTime(this, ext, suppressSeconds, suppressMilliseconds, includeOffset, extendedZone);\n      return c;\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime's date component\n     * @param {Object} opts - options\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example DateTime.utc(1982, 5, 25).toISODate() //=> '1982-05-25'\n     * @example DateTime.utc(1982, 5, 25).toISODate({ format: 'basic' }) //=> '19820525'\n     * @return {string}\n     */;\n    _proto.toISODate = function toISODate(_temp5) {\n      var _ref6 = _temp5 === void 0 ? {} : _temp5,\n        _ref6$format = _ref6.format,\n        format = _ref6$format === void 0 ? \"extended\" : _ref6$format;\n      if (!this.isValid) {\n        return null;\n      }\n      return _toISODate(this, format === \"extended\");\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime's week date\n     * @example DateTime.utc(1982, 5, 25).toISOWeekDate() //=> '1982-W21-2'\n     * @return {string}\n     */;\n    _proto.toISOWeekDate = function toISOWeekDate() {\n      return toTechFormat(this, \"kkkk-'W'WW-c\");\n    }\n\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime's time component\n     * @param {Object} opts - options\n     * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0\n     * @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @param {boolean} [opts.extendedZone=true] - add the time zone format extension\n     * @param {boolean} [opts.includePrefix=false] - include the `T` prefix\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime() //=> '07:34:19.361Z'\n     * @example DateTime.utc().set({ hour: 7, minute: 34, seconds: 0, milliseconds: 0 }).toISOTime({ suppressSeconds: true }) //=> '07:34Z'\n     * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ format: 'basic' }) //=> '073419.361Z'\n     * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ includePrefix: true }) //=> 'T07:34:19.361Z'\n     * @return {string}\n     */;\n    _proto.toISOTime = function toISOTime(_temp6) {\n      var _ref7 = _temp6 === void 0 ? {} : _temp6,\n        _ref7$suppressMillise = _ref7.suppressMilliseconds,\n        suppressMilliseconds = _ref7$suppressMillise === void 0 ? false : _ref7$suppressMillise,\n        _ref7$suppressSeconds = _ref7.suppressSeconds,\n        suppressSeconds = _ref7$suppressSeconds === void 0 ? false : _ref7$suppressSeconds,\n        _ref7$includeOffset = _ref7.includeOffset,\n        includeOffset = _ref7$includeOffset === void 0 ? true : _ref7$includeOffset,\n        _ref7$includePrefix = _ref7.includePrefix,\n        includePrefix = _ref7$includePrefix === void 0 ? false : _ref7$includePrefix,\n        _ref7$extendedZone = _ref7.extendedZone,\n        extendedZone = _ref7$extendedZone === void 0 ? false : _ref7$extendedZone,\n        _ref7$format = _ref7.format,\n        format = _ref7$format === void 0 ? \"extended\" : _ref7$format;\n      if (!this.isValid) {\n        return null;\n      }\n      var c = includePrefix ? \"T\" : \"\";\n      return c + _toISOTime(this, format === \"extended\", suppressSeconds, suppressMilliseconds, includeOffset, extendedZone);\n    }\n\n    /**\n     * Returns an RFC 2822-compatible string representation of this DateTime\n     * @example DateTime.utc(2014, 7, 13).toRFC2822() //=> 'Sun, 13 Jul 2014 00:00:00 +0000'\n     * @example DateTime.local(2014, 7, 13).toRFC2822() //=> 'Sun, 13 Jul 2014 00:00:00 -0400'\n     * @return {string}\n     */;\n    _proto.toRFC2822 = function toRFC2822() {\n      return toTechFormat(this, \"EEE, dd LLL yyyy HH:mm:ss ZZZ\", false);\n    }\n\n    /**\n     * Returns a string representation of this DateTime appropriate for use in HTTP headers. The output is always expressed in GMT.\n     * Specifically, the string conforms to RFC 1123.\n     * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1\n     * @example DateTime.utc(2014, 7, 13).toHTTP() //=> 'Sun, 13 Jul 2014 00:00:00 GMT'\n     * @example DateTime.utc(2014, 7, 13, 19).toHTTP() //=> 'Sun, 13 Jul 2014 19:00:00 GMT'\n     * @return {string}\n     */;\n    _proto.toHTTP = function toHTTP() {\n      return toTechFormat(this.toUTC(), \"EEE, dd LLL yyyy HH:mm:ss 'GMT'\");\n    }\n\n    /**\n     * Returns a string representation of this DateTime appropriate for use in SQL Date\n     * @example DateTime.utc(2014, 7, 13).toSQLDate() //=> '2014-07-13'\n     * @return {string}\n     */;\n    _proto.toSQLDate = function toSQLDate() {\n      if (!this.isValid) {\n        return null;\n      }\n      return _toISODate(this, true);\n    }\n\n    /**\n     * Returns a string representation of this DateTime appropriate for use in SQL Time\n     * @param {Object} opts - options\n     * @param {boolean} [opts.includeZone=false] - include the zone, such as 'America/New_York'. Overrides includeOffset.\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @param {boolean} [opts.includeOffsetSpace=true] - include the space between the time and the offset, such as '05:15:16.345 -04:00'\n     * @example DateTime.utc().toSQL() //=> '05:15:16.345'\n     * @example DateTime.now().toSQL() //=> '05:15:16.345 -04:00'\n     * @example DateTime.now().toSQL({ includeOffset: false }) //=> '05:15:16.345'\n     * @example DateTime.now().toSQL({ includeZone: false }) //=> '05:15:16.345 America/New_York'\n     * @return {string}\n     */;\n    _proto.toSQLTime = function toSQLTime(_temp7) {\n      var _ref8 = _temp7 === void 0 ? {} : _temp7,\n        _ref8$includeOffset = _ref8.includeOffset,\n        includeOffset = _ref8$includeOffset === void 0 ? true : _ref8$includeOffset,\n        _ref8$includeZone = _ref8.includeZone,\n        includeZone = _ref8$includeZone === void 0 ? false : _ref8$includeZone,\n        _ref8$includeOffsetSp = _ref8.includeOffsetSpace,\n        includeOffsetSpace = _ref8$includeOffsetSp === void 0 ? true : _ref8$includeOffsetSp;\n      var fmt = \"HH:mm:ss.SSS\";\n      if (includeZone || includeOffset) {\n        if (includeOffsetSpace) {\n          fmt += \" \";\n        }\n        if (includeZone) {\n          fmt += \"z\";\n        } else if (includeOffset) {\n          fmt += \"ZZ\";\n        }\n      }\n      return toTechFormat(this, fmt, true);\n    }\n\n    /**\n     * Returns a string representation of this DateTime appropriate for use in SQL DateTime\n     * @param {Object} opts - options\n     * @param {boolean} [opts.includeZone=false] - include the zone, such as 'America/New_York'. Overrides includeOffset.\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @param {boolean} [opts.includeOffsetSpace=true] - include the space between the time and the offset, such as '05:15:16.345 -04:00'\n     * @example DateTime.utc(2014, 7, 13).toSQL() //=> '2014-07-13 00:00:00.000 Z'\n     * @example DateTime.local(2014, 7, 13).toSQL() //=> '2014-07-13 00:00:00.000 -04:00'\n     * @example DateTime.local(2014, 7, 13).toSQL({ includeOffset: false }) //=> '2014-07-13 00:00:00.000'\n     * @example DateTime.local(2014, 7, 13).toSQL({ includeZone: true }) //=> '2014-07-13 00:00:00.000 America/New_York'\n     * @return {string}\n     */;\n    _proto.toSQL = function toSQL(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (!this.isValid) {\n        return null;\n      }\n      return this.toSQLDate() + \" \" + this.toSQLTime(opts);\n    }\n\n    /**\n     * Returns a string representation of this DateTime appropriate for debugging\n     * @return {string}\n     */;\n    _proto.toString = function toString() {\n      return this.isValid ? this.toISO() : INVALID;\n    }\n\n    /**\n     * Returns a string representation of this DateTime appropriate for the REPL.\n     * @return {string}\n     */;\n    _proto[_Symbol$for] = function () {\n      if (this.isValid) {\n        return \"DateTime { ts: \" + this.toISO() + \", zone: \" + this.zone.name + \", locale: \" + this.locale + \" }\";\n      } else {\n        return \"DateTime { Invalid, reason: \" + this.invalidReason + \" }\";\n      }\n    }\n\n    /**\n     * Returns the epoch milliseconds of this DateTime. Alias of {@link DateTime#toMillis}\n     * @return {number}\n     */;\n    _proto.valueOf = function valueOf() {\n      return this.toMillis();\n    }\n\n    /**\n     * Returns the epoch milliseconds of this DateTime.\n     * @return {number}\n     */;\n    _proto.toMillis = function toMillis() {\n      return this.isValid ? this.ts : NaN;\n    }\n\n    /**\n     * Returns the epoch seconds of this DateTime.\n     * @return {number}\n     */;\n    _proto.toSeconds = function toSeconds() {\n      return this.isValid ? this.ts / 1000 : NaN;\n    }\n\n    /**\n     * Returns the epoch seconds (as a whole number) of this DateTime.\n     * @return {number}\n     */;\n    _proto.toUnixInteger = function toUnixInteger() {\n      return this.isValid ? Math.floor(this.ts / 1000) : NaN;\n    }\n\n    /**\n     * Returns an ISO 8601 representation of this DateTime appropriate for use in JSON.\n     * @return {string}\n     */;\n    _proto.toJSON = function toJSON() {\n      return this.toISO();\n    }\n\n    /**\n     * Returns a BSON serializable equivalent to this DateTime.\n     * @return {Date}\n     */;\n    _proto.toBSON = function toBSON() {\n      return this.toJSDate();\n    }\n\n    /**\n     * Returns a JavaScript object with this DateTime's year, month, day, and so on.\n     * @param opts - options for generating the object\n     * @param {boolean} [opts.includeConfig=false] - include configuration attributes in the output\n     * @example DateTime.now().toObject() //=> { year: 2017, month: 4, day: 22, hour: 20, minute: 49, second: 42, millisecond: 268 }\n     * @return {Object}\n     */;\n    _proto.toObject = function toObject(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (!this.isValid) return {};\n      var base = _extends({}, this.c);\n      if (opts.includeConfig) {\n        base.outputCalendar = this.outputCalendar;\n        base.numberingSystem = this.loc.numberingSystem;\n        base.locale = this.loc.locale;\n      }\n      return base;\n    }\n\n    /**\n     * Returns a JavaScript Date equivalent to this DateTime.\n     * @return {Date}\n     */;\n    _proto.toJSDate = function toJSDate() {\n      return new Date(this.isValid ? this.ts : NaN);\n    }\n\n    // COMPARE\n\n    /**\n     * Return the difference between two DateTimes as a Duration.\n     * @param {DateTime} otherDateTime - the DateTime to compare this one to\n     * @param {string|string[]} [unit=['milliseconds']] - the unit or array of units (such as 'hours' or 'days') to include in the duration.\n     * @param {Object} opts - options that affect the creation of the Duration\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @example\n     * var i1 = DateTime.fromISO('1982-05-25T09:45'),\n     *     i2 = DateTime.fromISO('1983-10-14T10:30');\n     * i2.diff(i1).toObject() //=> { milliseconds: 43807500000 }\n     * i2.diff(i1, 'hours').toObject() //=> { hours: 12168.75 }\n     * i2.diff(i1, ['months', 'days']).toObject() //=> { months: 16, days: 19.03125 }\n     * i2.diff(i1, ['months', 'days', 'hours']).toObject() //=> { months: 16, days: 19, hours: 0.75 }\n     * @return {Duration}\n     */;\n    _proto.diff = function diff(otherDateTime, unit, opts) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (!this.isValid || !otherDateTime.isValid) {\n        return Duration.invalid(\"created by diffing an invalid DateTime\");\n      }\n      var durOpts = _extends({\n        locale: this.locale,\n        numberingSystem: this.numberingSystem\n      }, opts);\n      var units = maybeArray(unit).map(Duration.normalizeUnit),\n        otherIsLater = otherDateTime.valueOf() > this.valueOf(),\n        earlier = otherIsLater ? this : otherDateTime,\n        later = otherIsLater ? otherDateTime : this,\n        diffed = _diff(earlier, later, units, durOpts);\n      return otherIsLater ? diffed.negate() : diffed;\n    }\n\n    /**\n     * Return the difference between this DateTime and right now.\n     * See {@link DateTime#diff}\n     * @param {string|string[]} [unit=['milliseconds']] - the unit or units units (such as 'hours' or 'days') to include in the duration\n     * @param {Object} opts - options that affect the creation of the Duration\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @return {Duration}\n     */;\n    _proto.diffNow = function diffNow(unit, opts) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n      if (opts === void 0) {\n        opts = {};\n      }\n      return this.diff(DateTime.now(), unit, opts);\n    }\n\n    /**\n     * Return an Interval spanning between this DateTime and another DateTime\n     * @param {DateTime} otherDateTime - the other end point of the Interval\n     * @return {Interval}\n     */;\n    _proto.until = function until(otherDateTime) {\n      return this.isValid ? Interval.fromDateTimes(this, otherDateTime) : this;\n    }\n\n    /**\n     * Return whether this DateTime is in the same unit of time as another DateTime.\n     * Higher-order units must also be identical for this function to return `true`.\n     * Note that time zones are **ignored** in this comparison, which compares the **local** calendar time. Use {@link DateTime#setZone} to convert one of the dates if needed.\n     * @param {DateTime} otherDateTime - the other DateTime\n     * @param {string} unit - the unit of time to check sameness on\n     * @param {Object} opts - options\n     * @param {boolean} [opts.useLocaleWeeks=false] - If true, use weeks based on the locale, i.e. use the locale-dependent start of the week; only the locale of this DateTime is used\n     * @example DateTime.now().hasSame(otherDT, 'day'); //~> true if otherDT is in the same current calendar day\n     * @return {boolean}\n     */;\n    _proto.hasSame = function hasSame(otherDateTime, unit, opts) {\n      if (!this.isValid) return false;\n      var inputMs = otherDateTime.valueOf();\n      var adjustedToZone = this.setZone(otherDateTime.zone, {\n        keepLocalTime: true\n      });\n      return adjustedToZone.startOf(unit, opts) <= inputMs && inputMs <= adjustedToZone.endOf(unit, opts);\n    }\n\n    /**\n     * Equality check\n     * Two DateTimes are equal if and only if they represent the same millisecond, have the same zone and location, and are both valid.\n     * To compare just the millisecond values, use `+dt1 === +dt2`.\n     * @param {DateTime} other - the other DateTime\n     * @return {boolean}\n     */;\n    _proto.equals = function equals(other) {\n      return this.isValid && other.isValid && this.valueOf() === other.valueOf() && this.zone.equals(other.zone) && this.loc.equals(other.loc);\n    }\n\n    /**\n     * Returns a string representation of a this time relative to now, such as \"in two days\". Can only internationalize if your\n     * platform supports Intl.RelativeTimeFormat. Rounds down by default.\n     * @param {Object} options - options that affect the output\n     * @param {DateTime} [options.base=DateTime.now()] - the DateTime to use as the basis to which this time is compared. Defaults to now.\n     * @param {string} [options.style=\"long\"] - the style of units, must be \"long\", \"short\", or \"narrow\"\n     * @param {string|string[]} options.unit - use a specific unit or array of units; if omitted, or an array, the method will pick the best unit. Use an array or one of \"years\", \"quarters\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", or \"seconds\"\n     * @param {boolean} [options.round=true] - whether to round the numbers in the output.\n     * @param {number} [options.padding=0] - padding in milliseconds. This allows you to round up the result if it fits inside the threshold. Don't use in combination with {round: false} because the decimal output will include the padding.\n     * @param {string} options.locale - override the locale of this DateTime\n     * @param {string} options.numberingSystem - override the numberingSystem of this DateTime. The Intl system may choose not to honor this\n     * @example DateTime.now().plus({ days: 1 }).toRelative() //=> \"in 1 day\"\n     * @example DateTime.now().setLocale(\"es\").toRelative({ days: 1 }) //=> \"dentro de 1 d\u00eda\"\n     * @example DateTime.now().plus({ days: 1 }).toRelative({ locale: \"fr\" }) //=> \"dans 23 heures\"\n     * @example DateTime.now().minus({ days: 2 }).toRelative() //=> \"2 days ago\"\n     * @example DateTime.now().minus({ days: 2 }).toRelative({ unit: \"hours\" }) //=> \"48 hours ago\"\n     * @example DateTime.now().minus({ hours: 36 }).toRelative({ round: false }) //=> \"1.5 days ago\"\n     */;\n    _proto.toRelative = function toRelative(options) {\n      if (options === void 0) {\n        options = {};\n      }\n      if (!this.isValid) return null;\n      var base = options.base || DateTime.fromObject({}, {\n          zone: this.zone\n        }),\n        padding = options.padding ? this < base ? -options.padding : options.padding : 0;\n      var units = [\"years\", \"months\", \"days\", \"hours\", \"minutes\", \"seconds\"];\n      var unit = options.unit;\n      if (Array.isArray(options.unit)) {\n        units = options.unit;\n        unit = undefined;\n      }\n      return diffRelative(base, this.plus(padding), _extends({}, options, {\n        numeric: \"always\",\n        units: units,\n        unit: unit\n      }));\n    }\n\n    /**\n     * Returns a string representation of this date relative to today, such as \"yesterday\" or \"next month\".\n     * Only internationalizes on platforms that supports Intl.RelativeTimeFormat.\n     * @param {Object} options - options that affect the output\n     * @param {DateTime} [options.base=DateTime.now()] - the DateTime to use as the basis to which this time is compared. Defaults to now.\n     * @param {string} options.locale - override the locale of this DateTime\n     * @param {string} options.unit - use a specific unit; if omitted, the method will pick the unit. Use one of \"years\", \"quarters\", \"months\", \"weeks\", or \"days\"\n     * @param {string} options.numberingSystem - override the numberingSystem of this DateTime. The Intl system may choose not to honor this\n     * @example DateTime.now().plus({ days: 1 }).toRelativeCalendar() //=> \"tomorrow\"\n     * @example DateTime.now().setLocale(\"es\").plus({ days: 1 }).toRelative() //=> \"\"ma\u00f1ana\"\n     * @example DateTime.now().plus({ days: 1 }).toRelativeCalendar({ locale: \"fr\" }) //=> \"demain\"\n     * @example DateTime.now().minus({ days: 2 }).toRelativeCalendar() //=> \"2 days ago\"\n     */;\n    _proto.toRelativeCalendar = function toRelativeCalendar(options) {\n      if (options === void 0) {\n        options = {};\n      }\n      if (!this.isValid) return null;\n      return diffRelative(options.base || DateTime.fromObject({}, {\n        zone: this.zone\n      }), this, _extends({}, options, {\n        numeric: \"auto\",\n        units: [\"years\", \"months\", \"days\"],\n        calendary: true\n      }));\n    }\n\n    /**\n     * Return the min of several date times\n     * @param {...DateTime} dateTimes - the DateTimes from which to choose the minimum\n     * @return {DateTime} the min DateTime, or undefined if called with no argument\n     */;\n    DateTime.min = function min() {\n      for (var _len = arguments.length, dateTimes = new Array(_len), _key = 0; _key < _len; _key++) {\n        dateTimes[_key] = arguments[_key];\n      }\n      if (!dateTimes.every(DateTime.isDateTime)) {\n        throw new InvalidArgumentError(\"min requires all arguments be DateTimes\");\n      }\n      return bestBy(dateTimes, function (i) {\n        return i.valueOf();\n      }, Math.min);\n    }\n\n    /**\n     * Return the max of several date times\n     * @param {...DateTime} dateTimes - the DateTimes from which to choose the maximum\n     * @return {DateTime} the max DateTime, or undefined if called with no argument\n     */;\n    DateTime.max = function max() {\n      for (var _len2 = arguments.length, dateTimes = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n        dateTimes[_key2] = arguments[_key2];\n      }\n      if (!dateTimes.every(DateTime.isDateTime)) {\n        throw new InvalidArgumentError(\"max requires all arguments be DateTimes\");\n      }\n      return bestBy(dateTimes, function (i) {\n        return i.valueOf();\n      }, Math.max);\n    }\n\n    // MISC\n\n    /**\n     * Explain how a string would be parsed by fromFormat()\n     * @param {string} text - the string to parse\n     * @param {string} fmt - the format the string is expected to be in (see description)\n     * @param {Object} options - options taken by fromFormat()\n     * @return {Object}\n     */;\n    DateTime.fromFormatExplain = function fromFormatExplain(text, fmt, options) {\n      if (options === void 0) {\n        options = {};\n      }\n      var _options = options,\n        _options$locale = _options.locale,\n        locale = _options$locale === void 0 ? null : _options$locale,\n        _options$numberingSys = _options.numberingSystem,\n        numberingSystem = _options$numberingSys === void 0 ? null : _options$numberingSys,\n        localeToUse = Locale.fromOpts({\n          locale: locale,\n          numberingSystem: numberingSystem,\n          defaultToEN: true\n        });\n      return explainFromTokens(localeToUse, text, fmt);\n    }\n\n    /**\n     * @deprecated use fromFormatExplain instead\n     */;\n    DateTime.fromStringExplain = function fromStringExplain(text, fmt, options) {\n      if (options === void 0) {\n        options = {};\n      }\n      return DateTime.fromFormatExplain(text, fmt, options);\n    }\n\n    /**\n     * Build a parser for `fmt` using the given locale. This parser can be passed\n     * to {@link DateTime.fromFormatParser} to a parse a date in this format. This\n     * can be used to optimize cases where many dates need to be parsed in a\n     * specific format.\n     *\n     * @param {String} fmt - the format the string is expected to be in (see\n     * description)\n     * @param {Object} options - options used to set locale and numberingSystem\n     * for parser\n     * @returns {TokenParser} - opaque object to be used\n     */;\n    DateTime.buildFormatParser = function buildFormatParser(fmt, options) {\n      if (options === void 0) {\n        options = {};\n      }\n      var _options2 = options,\n        _options2$locale = _options2.locale,\n        locale = _options2$locale === void 0 ? null : _options2$locale,\n        _options2$numberingSy = _options2.numberingSystem,\n        numberingSystem = _options2$numberingSy === void 0 ? null : _options2$numberingSy,\n        localeToUse = Locale.fromOpts({\n          locale: locale,\n          numberingSystem: numberingSystem,\n          defaultToEN: true\n        });\n      return new TokenParser(localeToUse, fmt);\n    }\n\n    /**\n     * Create a DateTime from an input string and format parser.\n     *\n     * The format parser must have been created with the same locale as this call.\n     *\n     * @param {String} text - the string to parse\n     * @param {TokenParser} formatParser - parser from {@link DateTime.buildFormatParser}\n     * @param {Object} opts - options taken by fromFormat()\n     * @returns {DateTime}\n     */;\n    DateTime.fromFormatParser = function fromFormatParser(text, formatParser, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n      if (isUndefined(text) || isUndefined(formatParser)) {\n        throw new InvalidArgumentError(\"fromFormatParser requires an input string and a format parser\");\n      }\n      var _opts2 = opts,\n        _opts2$locale = _opts2.locale,\n        locale = _opts2$locale === void 0 ? null : _opts2$locale,\n        _opts2$numberingSyste = _opts2.numberingSystem,\n        numberingSystem = _opts2$numberingSyste === void 0 ? null : _opts2$numberingSyste,\n        localeToUse = Locale.fromOpts({\n          locale: locale,\n          numberingSystem: numberingSystem,\n          defaultToEN: true\n        });\n      if (!localeToUse.equals(formatParser.locale)) {\n        throw new InvalidArgumentError(\"fromFormatParser called with a locale of \" + localeToUse + \", \" + (\"but the format parser was created for \" + formatParser.locale));\n      }\n      var _formatParser$explain = formatParser.explainFromTokens(text),\n        result = _formatParser$explain.result,\n        zone = _formatParser$explain.zone,\n        specificOffset = _formatParser$explain.specificOffset,\n        invalidReason = _formatParser$explain.invalidReason;\n      if (invalidReason) {\n        return DateTime.invalid(invalidReason);\n      } else {\n        return parseDataToDateTime(result, zone, opts, \"format \" + formatParser.format, text, specificOffset);\n      }\n    }\n\n    // FORMAT PRESETS\n\n    /**\n     * {@link DateTime#toLocaleString} format like 10/14/1983\n     * @type {Object}\n     */;\n    _createClass(DateTime, [{\n      key: \"isValid\",\n      get: function get() {\n        return this.invalid === null;\n      }\n\n      /**\n       * Returns an error code if this DateTime is invalid, or null if the DateTime is valid\n       * @type {string}\n       */\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.invalid ? this.invalid.reason : null;\n      }\n\n      /**\n       * Returns an explanation of why this DateTime became invalid, or null if the DateTime is valid\n       * @type {string}\n       */\n    }, {\n      key: \"invalidExplanation\",\n      get: function get() {\n        return this.invalid ? this.invalid.explanation : null;\n      }\n\n      /**\n       * Get the locale of a DateTime, such 'en-GB'. The locale is used when formatting the DateTime\n       *\n       * @type {string}\n       */\n    }, {\n      key: \"locale\",\n      get: function get() {\n        return this.isValid ? this.loc.locale : null;\n      }\n\n      /**\n       * Get the numbering system of a DateTime, such 'beng'. The numbering system is used when formatting the DateTime\n       *\n       * @type {string}\n       */\n    }, {\n      key: \"numberingSystem\",\n      get: function get() {\n        return this.isValid ? this.loc.numberingSystem : null;\n      }\n\n      /**\n       * Get the output calendar of a DateTime, such 'islamic'. The output calendar is used when formatting the DateTime\n       *\n       * @type {string}\n       */\n    }, {\n      key: \"outputCalendar\",\n      get: function get() {\n        return this.isValid ? this.loc.outputCalendar : null;\n      }\n\n      /**\n       * Get the time zone associated with this DateTime.\n       * @type {Zone}\n       */\n    }, {\n      key: \"zone\",\n      get: function get() {\n        return this._zone;\n      }\n\n      /**\n       * Get the name of the time zone.\n       * @type {string}\n       */\n    }, {\n      key: \"zoneName\",\n      get: function get() {\n        return this.isValid ? this.zone.name : null;\n      }\n\n      /**\n       * Get the year\n       * @example DateTime.local(2017, 5, 25).year //=> 2017\n       * @type {number}\n       */\n    }, {\n      key: \"year\",\n      get: function get() {\n        return this.isValid ? this.c.year : NaN;\n      }\n\n      /**\n       * Get the quarter\n       * @example DateTime.local(2017, 5, 25).quarter //=> 2\n       * @type {number}\n       */\n    }, {\n      key: \"quarter\",\n      get: function get() {\n        return this.isValid ? Math.ceil(this.c.month / 3) : NaN;\n      }\n\n      /**\n       * Get the month (1-12).\n       * @example DateTime.local(2017, 5, 25).month //=> 5\n       * @type {number}\n       */\n    }, {\n      key: \"month\",\n      get: function get() {\n        return this.isValid ? this.c.month : NaN;\n      }\n\n      /**\n       * Get the day of the month (1-30ish).\n       * @example DateTime.local(2017, 5, 25).day //=> 25\n       * @type {number}\n       */\n    }, {\n      key: \"day\",\n      get: function get() {\n        return this.isValid ? this.c.day : NaN;\n      }\n\n      /**\n       * Get the hour of the day (0-23).\n       * @example DateTime.local(2017, 5, 25, 9).hour //=> 9\n       * @type {number}\n       */\n    }, {\n      key: \"hour\",\n      get: function get() {\n        return this.isValid ? this.c.hour : NaN;\n      }\n\n      /**\n       * Get the minute of the hour (0-59).\n       * @example DateTime.local(2017, 5, 25, 9, 30).minute //=> 30\n       * @type {number}\n       */\n    }, {\n      key: \"minute\",\n      get: function get() {\n        return this.isValid ? this.c.minute : NaN;\n      }\n\n      /**\n       * Get the second of the minute (0-59).\n       * @example DateTime.local(2017, 5, 25, 9, 30, 52).second //=> 52\n       * @type {number}\n       */\n    }, {\n      key: \"second\",\n      get: function get() {\n        return this.isValid ? this.c.second : NaN;\n      }\n\n      /**\n       * Get the millisecond of the second (0-999).\n       * @example DateTime.local(2017, 5, 25, 9, 30, 52, 654).millisecond //=> 654\n       * @type {number}\n       */\n    }, {\n      key: \"millisecond\",\n      get: function get() {\n        return this.isValid ? this.c.millisecond : NaN;\n      }\n\n      /**\n       * Get the week year\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2014, 12, 31).weekYear //=> 2015\n       * @type {number}\n       */\n    }, {\n      key: \"weekYear\",\n      get: function get() {\n        return this.isValid ? possiblyCachedWeekData(this).weekYear : NaN;\n      }\n\n      /**\n       * Get the week number of the week year (1-52ish).\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2017, 5, 25).weekNumber //=> 21\n       * @type {number}\n       */\n    }, {\n      key: \"weekNumber\",\n      get: function get() {\n        return this.isValid ? possiblyCachedWeekData(this).weekNumber : NaN;\n      }\n\n      /**\n       * Get the day of the week.\n       * 1 is Monday and 7 is Sunday\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2014, 11, 31).weekday //=> 4\n       * @type {number}\n       */\n    }, {\n      key: \"weekday\",\n      get: function get() {\n        return this.isValid ? possiblyCachedWeekData(this).weekday : NaN;\n      }\n\n      /**\n       * Returns true if this date is on a weekend according to the locale, false otherwise\n       * @returns {boolean}\n       */\n    }, {\n      key: \"isWeekend\",\n      get: function get() {\n        return this.isValid && this.loc.getWeekendDays().includes(this.weekday);\n      }\n\n      /**\n       * Get the day of the week according to the locale.\n       * 1 is the first day of the week and 7 is the last day of the week.\n       * If the locale assigns Sunday as the first day of the week, then a date which is a Sunday will return 1,\n       * @returns {number}\n       */\n    }, {\n      key: \"localWeekday\",\n      get: function get() {\n        return this.isValid ? possiblyCachedLocalWeekData(this).weekday : NaN;\n      }\n\n      /**\n       * Get the week number of the week year according to the locale. Different locales assign week numbers differently,\n       * because the week can start on different days of the week (see localWeekday) and because a different number of days\n       * is required for a week to count as the first week of a year.\n       * @returns {number}\n       */\n    }, {\n      key: \"localWeekNumber\",\n      get: function get() {\n        return this.isValid ? possiblyCachedLocalWeekData(this).weekNumber : NaN;\n      }\n\n      /**\n       * Get the week year according to the locale. Different locales assign week numbers (and therefor week years)\n       * differently, see localWeekNumber.\n       * @returns {number}\n       */\n    }, {\n      key: \"localWeekYear\",\n      get: function get() {\n        return this.isValid ? possiblyCachedLocalWeekData(this).weekYear : NaN;\n      }\n\n      /**\n       * Get the ordinal (meaning the day of the year)\n       * @example DateTime.local(2017, 5, 25).ordinal //=> 145\n       * @type {number|DateTime}\n       */\n    }, {\n      key: \"ordinal\",\n      get: function get() {\n        return this.isValid ? gregorianToOrdinal(this.c).ordinal : NaN;\n      }\n\n      /**\n       * Get the human readable short month name, such as 'Oct'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).monthShort //=> Oct\n       * @type {string}\n       */\n    }, {\n      key: \"monthShort\",\n      get: function get() {\n        return this.isValid ? Info.months(\"short\", {\n          locObj: this.loc\n        })[this.month - 1] : null;\n      }\n\n      /**\n       * Get the human readable long month name, such as 'October'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).monthLong //=> October\n       * @type {string}\n       */\n    }, {\n      key: \"monthLong\",\n      get: function get() {\n        return this.isValid ? Info.months(\"long\", {\n          locObj: this.loc\n        })[this.month - 1] : null;\n      }\n\n      /**\n       * Get the human readable short weekday, such as 'Mon'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).weekdayShort //=> Mon\n       * @type {string}\n       */\n    }, {\n      key: \"weekdayShort\",\n      get: function get() {\n        return this.isValid ? Info.weekdays(\"short\", {\n          locObj: this.loc\n        })[this.weekday - 1] : null;\n      }\n\n      /**\n       * Get the human readable long weekday, such as 'Monday'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).weekdayLong //=> Monday\n       * @type {string}\n       */\n    }, {\n      key: \"weekdayLong\",\n      get: function get() {\n        return this.isValid ? Info.weekdays(\"long\", {\n          locObj: this.loc\n        })[this.weekday - 1] : null;\n      }\n\n      /**\n       * Get the UTC offset of this DateTime in minutes\n       * @example DateTime.now().offset //=> -240\n       * @example DateTime.utc().offset //=> 0\n       * @type {number}\n       */\n    }, {\n      key: \"offset\",\n      get: function get() {\n        return this.isValid ? +this.o : NaN;\n      }\n\n      /**\n       * Get the short human name for the zone's current offset, for example \"EST\" or \"EDT\".\n       * Defaults to the system's locale if no locale has been specified\n       * @type {string}\n       */\n    }, {\n      key: \"offsetNameShort\",\n      get: function get() {\n        if (this.isValid) {\n          return this.zone.offsetName(this.ts, {\n            format: \"short\",\n            locale: this.locale\n          });\n        } else {\n          return null;\n        }\n      }\n\n      /**\n       * Get the long human name for the zone's current offset, for example \"Eastern Standard Time\" or \"Eastern Daylight Time\".\n       * Defaults to the system's locale if no locale has been specified\n       * @type {string}\n       */\n    }, {\n      key: \"offsetNameLong\",\n      get: function get() {\n        if (this.isValid) {\n          return this.zone.offsetName(this.ts, {\n            format: \"long\",\n            locale: this.locale\n          });\n        } else {\n          return null;\n        }\n      }\n\n      /**\n       * Get whether this zone's offset ever changes, as in a DST.\n       * @type {boolean}\n       */\n    }, {\n      key: \"isOffsetFixed\",\n      get: function get() {\n        return this.isValid ? this.zone.isUniversal : null;\n      }\n\n      /**\n       * Get whether the DateTime is in a DST.\n       * @type {boolean}\n       */\n    }, {\n      key: \"isInDST\",\n      get: function get() {\n        if (this.isOffsetFixed) {\n          return false;\n        } else {\n          return this.offset > this.set({\n            month: 1,\n            day: 1\n          }).offset || this.offset > this.set({\n            month: 5\n          }).offset;\n        }\n      }\n    }, {\n      key: \"isInLeapYear\",\n      get: function get() {\n        return isLeapYear(this.year);\n      }\n\n      /**\n       * Returns the number of days in this DateTime's month\n       * @example DateTime.local(2016, 2).daysInMonth //=> 29\n       * @example DateTime.local(2016, 3).daysInMonth //=> 31\n       * @type {number}\n       */\n    }, {\n      key: \"daysInMonth\",\n      get: function get() {\n        return daysInMonth(this.year, this.month);\n      }\n\n      /**\n       * Returns the number of days in this DateTime's year\n       * @example DateTime.local(2016).daysInYear //=> 366\n       * @example DateTime.local(2013).daysInYear //=> 365\n       * @type {number}\n       */\n    }, {\n      key: \"daysInYear\",\n      get: function get() {\n        return this.isValid ? daysInYear(this.year) : NaN;\n      }\n\n      /**\n       * Returns the number of weeks in this DateTime's year\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2004).weeksInWeekYear //=> 53\n       * @example DateTime.local(2013).weeksInWeekYear //=> 52\n       * @type {number}\n       */\n    }, {\n      key: \"weeksInWeekYear\",\n      get: function get() {\n        return this.isValid ? weeksInWeekYear(this.weekYear) : NaN;\n      }\n\n      /**\n       * Returns the number of weeks in this DateTime's local week year\n       * @example DateTime.local(2020, 6, {locale: 'en-US'}).weeksInLocalWeekYear //=> 52\n       * @example DateTime.local(2020, 6, {locale: 'de-DE'}).weeksInLocalWeekYear //=> 53\n       * @type {number}\n       */\n    }, {\n      key: \"weeksInLocalWeekYear\",\n      get: function get() {\n        return this.isValid ? weeksInWeekYear(this.localWeekYear, this.loc.getMinDaysInFirstWeek(), this.loc.getStartOfWeek()) : NaN;\n      }\n    }], [{\n      key: \"DATE_SHORT\",\n      get: function get() {\n        return DATE_SHORT;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Oct 14, 1983'\n       * @type {Object}\n       */\n    }, {\n      key: \"DATE_MED\",\n      get: function get() {\n        return DATE_MED;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Fri, Oct 14, 1983'\n       * @type {Object}\n       */\n    }, {\n      key: \"DATE_MED_WITH_WEEKDAY\",\n      get: function get() {\n        return DATE_MED_WITH_WEEKDAY;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'October 14, 1983'\n       * @type {Object}\n       */\n    }, {\n      key: \"DATE_FULL\",\n      get: function get() {\n        return DATE_FULL;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Tuesday, October 14, 1983'\n       * @type {Object}\n       */\n    }, {\n      key: \"DATE_HUGE\",\n      get: function get() {\n        return DATE_HUGE;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_SIMPLE\",\n      get: function get() {\n        return TIME_SIMPLE;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30:23 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_WITH_SECONDS\",\n      get: function get() {\n        return TIME_WITH_SECONDS;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30:23 AM EDT'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_WITH_SHORT_OFFSET\",\n      get: function get() {\n        return TIME_WITH_SHORT_OFFSET;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30:23 AM Eastern Daylight Time'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_WITH_LONG_OFFSET\",\n      get: function get() {\n        return TIME_WITH_LONG_OFFSET;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30', always 24-hour.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_24_SIMPLE\",\n      get: function get() {\n        return TIME_24_SIMPLE;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30:23', always 24-hour.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_24_WITH_SECONDS\",\n      get: function get() {\n        return TIME_24_WITH_SECONDS;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30:23 EDT', always 24-hour.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_24_WITH_SHORT_OFFSET\",\n      get: function get() {\n        return TIME_24_WITH_SHORT_OFFSET;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '09:30:23 Eastern Daylight Time', always 24-hour.\n       * @type {Object}\n       */\n    }, {\n      key: \"TIME_24_WITH_LONG_OFFSET\",\n      get: function get() {\n        return TIME_24_WITH_LONG_OFFSET;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '10/14/1983, 9:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_SHORT\",\n      get: function get() {\n        return DATETIME_SHORT;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like '10/14/1983, 9:30:33 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_SHORT_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_SHORT_WITH_SECONDS;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Oct 14, 1983, 9:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_MED\",\n      get: function get() {\n        return DATETIME_MED;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Oct 14, 1983, 9:30:33 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_MED_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_MED_WITH_SECONDS;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Fri, 14 Oct 1983, 9:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_MED_WITH_WEEKDAY\",\n      get: function get() {\n        return DATETIME_MED_WITH_WEEKDAY;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'October 14, 1983, 9:30 AM EDT'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_FULL\",\n      get: function get() {\n        return DATETIME_FULL;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'October 14, 1983, 9:30:33 AM EDT'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_FULL_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_FULL_WITH_SECONDS;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Friday, October 14, 1983, 9:30 AM Eastern Daylight Time'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_HUGE\",\n      get: function get() {\n        return DATETIME_HUGE;\n      }\n\n      /**\n       * {@link DateTime#toLocaleString} format like 'Friday, October 14, 1983, 9:30:33 AM Eastern Daylight Time'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n    }, {\n      key: \"DATETIME_HUGE_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_HUGE_WITH_SECONDS;\n      }\n    }]);\n    return DateTime;\n  }(Symbol.for(\"nodejs.util.inspect.custom\"));\n  function friendlyDateTime(dateTimeish) {\n    if (DateTime.isDateTime(dateTimeish)) {\n      return dateTimeish;\n    } else if (dateTimeish && dateTimeish.valueOf && isNumber(dateTimeish.valueOf())) {\n      return DateTime.fromJSDate(dateTimeish);\n    } else if (dateTimeish && typeof dateTimeish === \"object\") {\n      return DateTime.fromObject(dateTimeish);\n    } else {\n      throw new InvalidArgumentError(\"Unknown datetime argument: \" + dateTimeish + \", of type \" + typeof dateTimeish);\n    }\n  }\n\n  var VERSION = \"3.5.0\";\n\n  exports.DateTime = DateTime;\n  exports.Duration = Duration;\n  exports.FixedOffsetZone = FixedOffsetZone;\n  exports.IANAZone = IANAZone;\n  exports.Info = Info;\n  exports.Interval = Interval;\n  exports.InvalidZone = InvalidZone;\n  exports.Settings = Settings;\n  exports.SystemZone = SystemZone;\n  exports.VERSION = VERSION;\n  exports.Zone = Zone;\n\n  Object.defineProperty(exports, '__esModule', { value: true });\n\n  return exports;\n\n})({});\n// start Odoo customization\n// The following prevents luxon objects from being made reactive by Owl, because they are immutable\nluxon.DateTime.prototype[Symbol.toStringTag] = \"LuxonDateTime\";\nluxon.Duration.prototype[Symbol.toStringTag] = \"LuxonDuration\";\nluxon.Interval.prototype[Symbol.toStringTag] = \"LuxonInterval\";\nluxon.Settings.prototype[Symbol.toStringTag] = \"LuxonSettings\";\nluxon.Info.prototype[Symbol.toStringTag] = \"LuxonInfo\";\nluxon.Zone.prototype[Symbol.toStringTag] = \"LuxonZone\";\n// end Odoo customization\n//# sourceMappingURL=luxon.js.map\n", "// @odoo-module ignore\nif (!Object.hasOwn) {\n    Object.hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);\n}\n", "// @odoo-module ignore\nif (!Array.prototype.at) {\n    Object.defineProperty(Array.prototype, \"at\", {\n        enumerable: false,\n        value: function (index) {\n            if (index >= 0) {\n                return this[index];\n            }\n            return this[this.length + index];\n        }\n    });\n}\n", "export const difference = function (s) {\n    if (!(s instanceof Set)) {\n        throw new Error(\"argument must be a Set\");\n    }\n    return new Set([...this].filter((e) => !s.has(e)));\n};\n\n// Safari < 17 (09/2023) doesn't support Set.difference, but this version is\n// quite recent enough for **public** users\nif (!Set.prototype.difference) {\n    Object.defineProperty(Set.prototype, \"difference\", {\n        enumerable: false,\n        value: difference,\n    });\n}\n", "// @odoo-module ignore\n\n(function () {\n'use strict';\n\n/**\n * This file makes sure textarea elements with a specific editor class are\n * tweaked as soon as the DOM is ready so that they appear to be loading.\n *\n * They must then be loaded using standard Odoo modules system. In particular,\n * @see @website_forum/static/src/interactions/website_forum\n */\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    // Standard loop for better browser support\n    var textareaEls = document.querySelectorAll('textarea.o_wysiwyg_loader');\n    for (var i = 0; i < textareaEls.length; i++) {\n        var textarea = textareaEls[i];\n        var wrapper = document.createElement('div');\n        wrapper.classList.add('position-relative', 'o_wysiwyg_textarea_wrapper');\n\n        var loadingElement = document.createElement('div');\n        loadingElement.classList.add('o_wysiwyg_loading');\n        var loadingIcon = document.createElement('i');\n        loadingIcon.classList.add('text-600', 'text-center',\n            'fa', 'fa-circle-o-notch', 'fa-spin', 'fa-2x');\n        loadingElement.appendChild(loadingIcon);\n        wrapper.appendChild(loadingElement);\n\n        textarea.parentNode.insertBefore(wrapper, textarea);\n        wrapper.insertBefore(textarea, loadingElement);\n    }\n});\n\n})();\n", "(function (exports) {\r\n    'use strict';\r\n\r\n    function filterOutModifiersFromData(dataList) {\r\n        dataList = dataList.slice();\r\n        const modifiers = [];\r\n        let elm;\r\n        while ((elm = dataList[0]) && typeof elm === \"string\") {\r\n            modifiers.push(dataList.shift());\r\n        }\r\n        return { modifiers, data: dataList };\r\n    }\r\n    const config = {\r\n        // whether or not blockdom should normalize DOM whenever a block is created.\r\n        // Normalizing dom mean removing empty text nodes (or containing only spaces)\r\n        shouldNormalizeDom: true,\r\n        // this is the main event handler. Every event handler registered with blockdom\r\n        // will go through this function, giving it the data registered in the block\r\n        // and the event\r\n        mainEventHandler: (data, ev, currentTarget) => {\r\n            if (typeof data === \"function\") {\r\n                data(ev);\r\n            }\r\n            else if (Array.isArray(data)) {\r\n                data = filterOutModifiersFromData(data).data;\r\n                data[0](data[1], ev);\r\n            }\r\n            return false;\r\n        },\r\n    };\r\n\r\n    // -----------------------------------------------------------------------------\r\n    // Toggler node\r\n    // -----------------------------------------------------------------------------\r\n    class VToggler {\r\n        constructor(key, child) {\r\n            this.key = key;\r\n            this.child = child;\r\n        }\r\n        mount(parent, afterNode) {\r\n            this.parentEl = parent;\r\n            this.child.mount(parent, afterNode);\r\n        }\r\n        moveBeforeDOMNode(node, parent) {\r\n            this.child.moveBeforeDOMNode(node, parent);\r\n        }\r\n        moveBeforeVNode(other, afterNode) {\r\n            this.moveBeforeDOMNode((other && other.firstNode()) || afterNode);\r\n        }\r\n        patch(other, withBeforeRemove) {\r\n            if (this === other) {\r\n                return;\r\n            }\r\n            let child1 = this.child;\r\n            let child2 = other.child;\r\n            if (this.key === other.key) {\r\n                child1.patch(child2, withBeforeRemove);\r\n            }\r\n            else {\r\n                child2.mount(this.parentEl, child1.firstNode());\r\n                if (withBeforeRemove) {\r\n                    child1.beforeRemove();\r\n                }\r\n                child1.remove();\r\n                this.child = child2;\r\n                this.key = other.key;\r\n            }\r\n        }\r\n        beforeRemove() {\r\n            this.child.beforeRemove();\r\n        }\r\n        remove() {\r\n            this.child.remove();\r\n        }\r\n        firstNode() {\r\n            return this.child.firstNode();\r\n        }\r\n        toString() {\r\n            return this.child.toString();\r\n        }\r\n    }\r\n    function toggler(key, child) {\r\n        return new VToggler(key, child);\r\n    }\r\n\r\n    // Custom error class that wraps error that happen in the owl lifecycle\r\n    class OwlError extends Error {\r\n    }\r\n\r\n    const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype;\r\n    const tokenList = DOMTokenList.prototype;\r\n    const tokenListAdd = tokenList.add;\r\n    const tokenListRemove = tokenList.remove;\r\n    const isArray = Array.isArray;\r\n    const { split, trim } = String.prototype;\r\n    const wordRegexp = /\\s+/;\r\n    /**\r\n     * We regroup here all code related to updating attributes in a very loose sense:\r\n     * attributes, properties and classs are all managed by the functions in this\r\n     * file.\r\n     */\r\n    function setAttribute(key, value) {\r\n        switch (value) {\r\n            case false:\r\n            case undefined:\r\n                removeAttribute.call(this, key);\r\n                break;\r\n            case true:\r\n                elemSetAttribute.call(this, key, \"\");\r\n                break;\r\n            default:\r\n                elemSetAttribute.call(this, key, value);\r\n        }\r\n    }\r\n    function createAttrUpdater(attr) {\r\n        return function (value) {\r\n            setAttribute.call(this, attr, value);\r\n        };\r\n    }\r\n    function attrsSetter(attrs) {\r\n        if (isArray(attrs)) {\r\n            if (attrs[0] === \"class\") {\r\n                setClass.call(this, attrs[1]);\r\n            }\r\n            else {\r\n                setAttribute.call(this, attrs[0], attrs[1]);\r\n            }\r\n        }\r\n        else {\r\n            for (let k in attrs) {\r\n                if (k === \"class\") {\r\n                    setClass.call(this, attrs[k]);\r\n                }\r\n                else {\r\n                    setAttribute.call(this, k, attrs[k]);\r\n                }\r\n            }\r\n        }\r\n    }\r\n    function attrsUpdater(attrs, oldAttrs) {\r\n        if (isArray(attrs)) {\r\n            const name = attrs[0];\r\n            const val = attrs[1];\r\n            if (name === oldAttrs[0]) {\r\n                if (val === oldAttrs[1]) {\r\n                    return;\r\n                }\r\n                if (name === \"class\") {\r\n                    updateClass.call(this, val, oldAttrs[1]);\r\n                }\r\n                else {\r\n                    setAttribute.call(this, name, val);\r\n                }\r\n            }\r\n            else {\r\n                removeAttribute.call(this, oldAttrs[0]);\r\n                setAttribute.call(this, name, val);\r\n            }\r\n        }\r\n        else {\r\n            for (let k in oldAttrs) {\r\n                if (!(k in attrs)) {\r\n                    if (k === \"class\") {\r\n                        updateClass.call(this, \"\", oldAttrs[k]);\r\n                    }\r\n                    else {\r\n                        removeAttribute.call(this, k);\r\n                    }\r\n                }\r\n            }\r\n            for (let k in attrs) {\r\n                const val = attrs[k];\r\n                if (val !== oldAttrs[k]) {\r\n                    if (k === \"class\") {\r\n                        updateClass.call(this, val, oldAttrs[k]);\r\n                    }\r\n                    else {\r\n                        setAttribute.call(this, k, val);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n    function toClassObj(expr) {\r\n        const result = {};\r\n        switch (typeof expr) {\r\n            case \"string\":\r\n                // we transform here a list of classes into an object:\r\n                //  'hey you' becomes {hey: true, you: true}\r\n                const str = trim.call(expr);\r\n                if (!str) {\r\n                    return {};\r\n                }\r\n                let words = split.call(str, wordRegexp);\r\n                for (let i = 0, l = words.length; i < l; i++) {\r\n                    result[words[i]] = true;\r\n                }\r\n                return result;\r\n            case \"object\":\r\n                // this is already an object but we may need to split keys:\r\n                // {'a': true, 'b c': true} should become {a: true, b: true, c: true}\r\n                for (let key in expr) {\r\n                    const value = expr[key];\r\n                    if (value) {\r\n                        key = trim.call(key);\r\n                        if (!key) {\r\n                            continue;\r\n                        }\r\n                        const words = split.call(key, wordRegexp);\r\n                        for (let word of words) {\r\n                            result[word] = value;\r\n                        }\r\n                    }\r\n                }\r\n                return result;\r\n            case \"undefined\":\r\n                return {};\r\n            case \"number\":\r\n                return { [expr]: true };\r\n            default:\r\n                return { [expr]: true };\r\n        }\r\n    }\r\n    function setClass(val) {\r\n        val = val === \"\" ? {} : toClassObj(val);\r\n        // add classes\r\n        const cl = this.classList;\r\n        for (let c in val) {\r\n            tokenListAdd.call(cl, c);\r\n        }\r\n    }\r\n    function updateClass(val, oldVal) {\r\n        oldVal = oldVal === \"\" ? {} : toClassObj(oldVal);\r\n        val = val === \"\" ? {} : toClassObj(val);\r\n        const cl = this.classList;\r\n        // remove classes\r\n        for (let c in oldVal) {\r\n            if (!(c in val)) {\r\n                tokenListRemove.call(cl, c);\r\n            }\r\n        }\r\n        // add classes\r\n        for (let c in val) {\r\n            if (!(c in oldVal)) {\r\n                tokenListAdd.call(cl, c);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Creates a batched version of a callback so that all calls to it in the same\r\n     * microtick will only call the original callback once.\r\n     *\r\n     * @param callback the callback to batch\r\n     * @returns a batched version of the original callback\r\n     */\r\n    function batched(callback) {\r\n        let scheduled = false;\r\n        return async (...args) => {\r\n            if (!scheduled) {\r\n                scheduled = true;\r\n                await Promise.resolve();\r\n                scheduled = false;\r\n                callback(...args);\r\n            }\r\n        };\r\n    }\r\n    /**\r\n     * Determine whether the given element is contained in its ownerDocument:\r\n     * either directly or with a shadow root in between.\r\n     */\r\n    function inOwnerDocument(el) {\r\n        if (!el) {\r\n            return false;\r\n        }\r\n        if (el.ownerDocument.contains(el)) {\r\n            return true;\r\n        }\r\n        const rootNode = el.getRootNode();\r\n        return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host);\r\n    }\r\n    /**\r\n     * Determine whether the given element is contained in a specific root documnet:\r\n     * either directly or with a shadow root in between or in an iframe.\r\n     */\r\n    function isAttachedToDocument(element, documentElement) {\r\n        let current = element;\r\n        const shadowRoot = documentElement.defaultView.ShadowRoot;\r\n        while (current) {\r\n            if (current === documentElement) {\r\n                return true;\r\n            }\r\n            if (current.parentNode) {\r\n                current = current.parentNode;\r\n            }\r\n            else if (current instanceof shadowRoot && current.host) {\r\n                current = current.host;\r\n            }\r\n            else {\r\n                return false;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n    function validateTarget(target) {\r\n        // Get the document and HTMLElement corresponding to the target to allow mounting in iframes\r\n        const document = target && target.ownerDocument;\r\n        if (document) {\r\n            if (!document.defaultView) {\r\n                throw new OwlError(\"Cannot mount a component: the target document is not attached to a window (defaultView is missing)\");\r\n            }\r\n            const HTMLElement = document.defaultView.HTMLElement;\r\n            if (target instanceof HTMLElement || target instanceof ShadowRoot) {\r\n                if (!isAttachedToDocument(target, document)) {\r\n                    throw new OwlError(\"Cannot mount a component on a detached dom node\");\r\n                }\r\n                return;\r\n            }\r\n        }\r\n        throw new OwlError(\"Cannot mount component: the target is not a valid DOM element\");\r\n    }\r\n    class EventBus extends EventTarget {\r\n        trigger(name, payload) {\r\n            this.dispatchEvent(new CustomEvent(name, { detail: payload }));\r\n        }\r\n    }\r\n    function whenReady(fn) {\r\n        return new Promise(function (resolve) {\r\n            if (document.readyState !== \"loading\") {\r\n                resolve(true);\r\n            }\r\n            else {\r\n                document.addEventListener(\"DOMContentLoaded\", resolve, false);\r\n            }\r\n        }).then(fn || function () { });\r\n    }\r\n    async function loadFile(url) {\r\n        const result = await fetch(url);\r\n        if (!result.ok) {\r\n            throw new OwlError(\"Error while fetching xml templates\");\r\n        }\r\n        return await result.text();\r\n    }\r\n    /*\r\n     * This class just transports the fact that a string is safe\r\n     * to be injected as HTML. Overriding a JS primitive is quite painful though\r\n     * so we need to redfine toString and valueOf.\r\n     */\r\n    class Markup extends String {\r\n    }\r\n    function htmlEscape(str) {\r\n        if (str instanceof Markup) {\r\n            return str;\r\n        }\r\n        if (str === undefined) {\r\n            return markup(\"\");\r\n        }\r\n        if (typeof str === \"number\") {\r\n            return markup(String(str));\r\n        }\r\n        [\r\n            [\"&\", \"&amp;\"],\r\n            [\"<\", \"&lt;\"],\r\n            [\">\", \"&gt;\"],\r\n            [\"'\", \"&#x27;\"],\r\n            ['\"', \"&quot;\"],\r\n            [\"`\", \"&#x60;\"],\r\n        ].forEach((pairs) => {\r\n            str = String(str).replace(new RegExp(pairs[0], \"g\"), pairs[1]);\r\n        });\r\n        return markup(str);\r\n    }\r\n    function markup(valueOrStrings, ...placeholders) {\r\n        if (!Array.isArray(valueOrStrings)) {\r\n            return new Markup(valueOrStrings);\r\n        }\r\n        const strings = valueOrStrings;\r\n        let acc = \"\";\r\n        let i = 0;\r\n        for (; i < placeholders.length; ++i) {\r\n            acc += strings[i] + htmlEscape(placeholders[i]);\r\n        }\r\n        acc += strings[i];\r\n        return new Markup(acc);\r\n    }\r\n\r\n    function createEventHandler(rawEvent) {\r\n        const eventName = rawEvent.split(\".\")[0];\r\n        const capture = rawEvent.includes(\".capture\");\r\n        if (rawEvent.includes(\".synthetic\")) {\r\n            return createSyntheticHandler(eventName, capture);\r\n        }\r\n        else {\r\n            return createElementHandler(eventName, capture);\r\n        }\r\n    }\r\n    // Native listener\r\n    let nextNativeEventId = 1;\r\n    function createElementHandler(evName, capture = false) {\r\n        let eventKey = `__event__${evName}_${nextNativeEventId++}`;\r\n        if (capture) {\r\n            eventKey = `${eventKey}_capture`;\r\n        }\r\n        function listener(ev) {\r\n            const currentTarget = ev.currentTarget;\r\n            if (!currentTarget || !inOwnerDocument(currentTarget))\r\n                return;\r\n            const data = currentTarget[eventKey];\r\n            if (!data)\r\n                return;\r\n            config.mainEventHandler(data, ev, currentTarget);\r\n        }\r\n        function setup(data) {\r\n            this[eventKey] = data;\r\n            this.addEventListener(evName, listener, { capture });\r\n        }\r\n        function remove() {\r\n            delete this[eventKey];\r\n            this.removeEventListener(evName, listener, { capture });\r\n        }\r\n        function update(data) {\r\n            this[eventKey] = data;\r\n        }\r\n        return { setup, update, remove };\r\n    }\r\n    // Synthetic handler: a form of event delegation that allows placing only one\r\n    // listener per event type.\r\n    let nextSyntheticEventId = 1;\r\n    function createSyntheticHandler(evName, capture = false) {\r\n        let eventKey = `__event__synthetic_${evName}`;\r\n        if (capture) {\r\n            eventKey = `${eventKey}_capture`;\r\n        }\r\n        setupSyntheticEvent(evName, eventKey, capture);\r\n        const currentId = nextSyntheticEventId++;\r\n        function setup(data) {\r\n            const _data = this[eventKey] || {};\r\n            _data[currentId] = data;\r\n            this[eventKey] = _data;\r\n        }\r\n        function remove() {\r\n            delete this[eventKey];\r\n        }\r\n        return { setup, update: setup, remove };\r\n    }\r\n    function nativeToSyntheticEvent(eventKey, event) {\r\n        let dom = event.target;\r\n        while (dom !== null) {\r\n            const _data = dom[eventKey];\r\n            if (_data) {\r\n                for (const data of Object.values(_data)) {\r\n                    const stopped = config.mainEventHandler(data, event, dom);\r\n                    if (stopped)\r\n                        return;\r\n                }\r\n            }\r\n            dom = dom.parentNode;\r\n        }\r\n    }\r\n    const CONFIGURED_SYNTHETIC_EVENTS = {};\r\n    function setupSyntheticEvent(evName, eventKey, capture = false) {\r\n        if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) {\r\n            return;\r\n        }\r\n        document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), {\r\n            capture,\r\n        });\r\n        CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true;\r\n    }\r\n\r\n    const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p);\r\n    const nodeProto$4 = Node.prototype;\r\n    const nodeInsertBefore$3 = nodeProto$4.insertBefore;\r\n    const nodeSetTextContent$1 = getDescriptor$3(nodeProto$4, \"textContent\").set;\r\n    const nodeRemoveChild$3 = nodeProto$4.removeChild;\r\n    // -----------------------------------------------------------------------------\r\n    // Multi NODE\r\n    // -----------------------------------------------------------------------------\r\n    class VMulti {\r\n        constructor(children) {\r\n            this.children = children;\r\n        }\r\n        mount(parent, afterNode) {\r\n            const children = this.children;\r\n            const l = children.length;\r\n            const anchors = new Array(l);\r\n            for (let i = 0; i < l; i++) {\r\n                let child = children[i];\r\n                if (child) {\r\n                    child.mount(parent, afterNode);\r\n                }\r\n                else {\r\n                    const childAnchor = document.createTextNode(\"\");\r\n                    anchors[i] = childAnchor;\r\n                    nodeInsertBefore$3.call(parent, childAnchor, afterNode);\r\n                }\r\n            }\r\n            this.anchors = anchors;\r\n            this.parentEl = parent;\r\n        }\r\n        moveBeforeDOMNode(node, parent = this.parentEl) {\r\n            this.parentEl = parent;\r\n            const children = this.children;\r\n            const anchors = this.anchors;\r\n            for (let i = 0, l = children.length; i < l; i++) {\r\n                let child = children[i];\r\n                if (child) {\r\n                    child.moveBeforeDOMNode(node, parent);\r\n                }\r\n                else {\r\n                    const anchor = anchors[i];\r\n                    nodeInsertBefore$3.call(parent, anchor, node);\r\n                }\r\n            }\r\n        }\r\n        moveBeforeVNode(other, afterNode) {\r\n            if (other) {\r\n                const next = other.children[0];\r\n                afterNode = (next ? next.firstNode() : other.anchors[0]) || null;\r\n            }\r\n            const children = this.children;\r\n            const parent = this.parentEl;\r\n            const anchors = this.anchors;\r\n            for (let i = 0, l = children.length; i < l; i++) {\r\n                let child = children[i];\r\n                if (child) {\r\n                    child.moveBeforeVNode(null, afterNode);\r\n                }\r\n                else {\r\n                    const anchor = anchors[i];\r\n                    nodeInsertBefore$3.call(parent, anchor, afterNode);\r\n                }\r\n            }\r\n        }\r\n        patch(other, withBeforeRemove) {\r\n            if (this === other) {\r\n                return;\r\n            }\r\n            const children1 = this.children;\r\n            const children2 = other.children;\r\n            const anchors = this.anchors;\r\n            const parentEl = this.parentEl;\r\n            for (let i = 0, l = children1.length; i < l; i++) {\r\n                const vn1 = children1[i];\r\n                const vn2 = children2[i];\r\n                if (vn1) {\r\n                    if (vn2) {\r\n                        vn1.patch(vn2, withBeforeRemove);\r\n                    }\r\n                    else {\r\n                        const afterNode = vn1.firstNode();\r\n                        const anchor = document.createTextNode(\"\");\r\n                        anchors[i] = anchor;\r\n                        nodeInsertBefore$3.call(parentEl, anchor, afterNode);\r\n                        if (withBeforeRemove) {\r\n                            vn1.beforeRemove();\r\n                        }\r\n                        vn1.remove();\r\n                        children1[i] = undefined;\r\n                    }\r\n                }\r\n                else if (vn2) {\r\n                    children1[i] = vn2;\r\n                    const anchor = anchors[i];\r\n                    vn2.mount(parentEl, anchor);\r\n                    nodeRemoveChild$3.call(parentEl, anchor);\r\n                }\r\n            }\r\n        }\r\n        beforeRemove() {\r\n            const children = this.children;\r\n            for (let i = 0, l = children.length; i < l; i++) {\r\n                const child = children[i];\r\n                if (child) {\r\n                    child.beforeRemove();\r\n                }\r\n            }\r\n        }\r\n        remove() {\r\n            const parentEl = this.parentEl;\r\n            if (this.isOnlyChild) {\r\n                nodeSetTextContent$1.call(parentEl, \"\");\r\n            }\r\n            else {\r\n                const children = this.children;\r\n                const anchors = this.anchors;\r\n                for (let i = 0, l = children.length; i < l; i++) {\r\n                    const child = children[i];\r\n                    if (child) {\r\n                        child.remove();\r\n                    }\r\n                    else {\r\n                        nodeRemoveChild$3.call(parentEl, anchors[i]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        firstNode() {\r\n            const child = this.children[0];\r\n            return child ? child.firstNode() : this.anchors[0];\r\n        }\r\n        toString() {\r\n            return this.children.map((c) => (c ? c.toString() : \"\")).join(\"\");\r\n        }\r\n    }\r\n    function multi(children) {\r\n        return new VMulti(children);\r\n    }\r\n\r\n    const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p);\r\n    const nodeProto$3 = Node.prototype;\r\n    const characterDataProto$1 = CharacterData.prototype;\r\n    const nodeInsertBefore$2 = nodeProto$3.insertBefore;\r\n    const characterDataSetData$1 = getDescriptor$2(characterDataProto$1, \"data\").set;\r\n    const nodeRemoveChild$2 = nodeProto$3.removeChild;\r\n    class VSimpleNode {\r\n        constructor(text) {\r\n            this.text = text;\r\n        }\r\n        mountNode(node, parent, afterNode) {\r\n            this.parentEl = parent;\r\n            nodeInsertBefore$2.call(parent, node, afterNode);\r\n            this.el = node;\r\n        }\r\n        moveBeforeDOMNode(node, parent = this.parentEl) {\r\n            this.parentEl = parent;\r\n            nodeInsertBefore$2.call(parent, this.el, node);\r\n        }\r\n        moveBeforeVNode(other, afterNode) {\r\n            nodeInsertBefore$2.call(this.parentEl, this.el, other ? other.el : afterNode);\r\n        }\r\n        beforeRemove() { }\r\n        remove() {\r\n            nodeRemoveChild$2.call(this.parentEl, this.el);\r\n        }\r\n        firstNode() {\r\n            return this.el;\r\n        }\r\n        toString() {\r\n            return this.text;\r\n        }\r\n    }\r\n    class VText$1 extends VSimpleNode {\r\n        mount(parent, afterNode) {\r\n            this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode);\r\n        }\r\n        patch(other) {\r\n            const text2 = other.text;\r\n            if (this.text !== text2) {\r\n                characterDataSetData$1.call(this.el, toText(text2));\r\n                this.text = text2;\r\n            }\r\n        }\r\n    }\r\n    class VComment extends VSimpleNode {\r\n        mount(parent, afterNode) {\r\n            this.mountNode(document.createComment(toText(this.text)), parent, afterNode);\r\n        }\r\n        patch() { }\r\n    }\r\n    function text(str) {\r\n        return new VText$1(str);\r\n    }\r\n    function comment(str) {\r\n        return new VComment(str);\r\n    }\r\n    function toText(value) {\r\n        switch (typeof value) {\r\n            case \"string\":\r\n                return value;\r\n            case \"number\":\r\n                return String(value);\r\n            case \"boolean\":\r\n                return value ? \"true\" : \"false\";\r\n            default:\r\n                return value || \"\";\r\n        }\r\n    }\r\n\r\n    const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p);\r\n    const nodeProto$2 = Node.prototype;\r\n    const elementProto = Element.prototype;\r\n    const characterDataProto = CharacterData.prototype;\r\n    const characterDataSetData = getDescriptor$1(characterDataProto, \"data\").set;\r\n    const nodeGetFirstChild = getDescriptor$1(nodeProto$2, \"firstChild\").get;\r\n    const nodeGetNextSibling = getDescriptor$1(nodeProto$2, \"nextSibling\").get;\r\n    const NO_OP = () => { };\r\n    function makePropSetter(name) {\r\n        return function setProp(value) {\r\n            // support 0, fallback to empty string for other falsy values\r\n            this[name] = value === 0 ? 0 : value ? value.valueOf() : \"\";\r\n        };\r\n    }\r\n    const cache$1 = {};\r\n    /**\r\n     * Compiling blocks is a multi-step process:\r\n     *\r\n     * 1. build an IntermediateTree from the HTML element. This intermediate tree\r\n     *    is a binary tree structure that encode dynamic info sub nodes, and the\r\n     *    path required to reach them\r\n     * 2. process the tree to build a block context, which is an object that aggregate\r\n     *    all dynamic info in a list, and also, all ref indexes.\r\n     * 3. process the context to build appropriate builder/setter functions\r\n     * 4. make a dynamic block class, which will efficiently collect references and\r\n     *    create/update dynamic locations/children\r\n     *\r\n     * @param str\r\n     * @returns a new block type, that can build concrete blocks\r\n     */\r\n    function createBlock(str) {\r\n        if (str in cache$1) {\r\n            return cache$1[str];\r\n        }\r\n        // step 0: prepare html base element\r\n        const doc = new DOMParser().parseFromString(`<t>${str}</t>`, \"text/xml\");\r\n        const node = doc.firstChild.firstChild;\r\n        if (config.shouldNormalizeDom) {\r\n            normalizeNode(node);\r\n        }\r\n        // step 1: prepare intermediate tree\r\n        const tree = buildTree(node);\r\n        // step 2: prepare block context\r\n        const context = buildContext(tree);\r\n        // step 3: build the final block class\r\n        const template = tree.el;\r\n        const Block = buildBlock(template, context);\r\n        cache$1[str] = Block;\r\n        return Block;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Helper\r\n    // -----------------------------------------------------------------------------\r\n    function normalizeNode(node) {\r\n        if (node.nodeType === Node.TEXT_NODE) {\r\n            if (!/\\S/.test(node.textContent)) {\r\n                node.remove();\r\n                return;\r\n            }\r\n        }\r\n        if (node.nodeType === Node.ELEMENT_NODE) {\r\n            if (node.tagName === \"pre\") {\r\n                return;\r\n            }\r\n        }\r\n        for (let i = node.childNodes.length - 1; i >= 0; --i) {\r\n            normalizeNode(node.childNodes.item(i));\r\n        }\r\n    }\r\n    function buildTree(node, parent = null, domParentTree = null) {\r\n        switch (node.nodeType) {\r\n            case Node.ELEMENT_NODE: {\r\n                // HTMLElement\r\n                let currentNS = domParentTree && domParentTree.currentNS;\r\n                const tagName = node.tagName;\r\n                let el = undefined;\r\n                const info = [];\r\n                if (tagName.startsWith(\"block-text-\")) {\r\n                    const index = parseInt(tagName.slice(11), 10);\r\n                    info.push({ type: \"text\", idx: index });\r\n                    el = document.createTextNode(\"\");\r\n                }\r\n                if (tagName.startsWith(\"block-child-\")) {\r\n                    if (!domParentTree.isRef) {\r\n                        addRef(domParentTree);\r\n                    }\r\n                    const index = parseInt(tagName.slice(12), 10);\r\n                    info.push({ type: \"child\", idx: index });\r\n                    el = document.createTextNode(\"\");\r\n                }\r\n                currentNS || (currentNS = node.namespaceURI);\r\n                if (!el) {\r\n                    el = currentNS\r\n                        ? document.createElementNS(currentNS, tagName)\r\n                        : document.createElement(tagName);\r\n                }\r\n                if (el instanceof Element) {\r\n                    if (!domParentTree) {\r\n                        // some html elements may have side effects when setting their attributes.\r\n                        // For example, setting the src attribute of an <img/> will trigger a\r\n                        // request to get the corresponding image. This is something that we\r\n                        // don't want at compile time. We avoid that by putting the content of\r\n                        // the block in a <template/> element\r\n                        const fragment = document.createElement(\"template\").content;\r\n                        fragment.appendChild(el);\r\n                    }\r\n                    const attrs = node.attributes;\r\n                    for (let i = 0; i < attrs.length; i++) {\r\n                        const attrName = attrs[i].name;\r\n                        const attrValue = attrs[i].value;\r\n                        if (attrName.startsWith(\"block-handler-\")) {\r\n                            const idx = parseInt(attrName.slice(14), 10);\r\n                            info.push({\r\n                                type: \"handler\",\r\n                                idx,\r\n                                event: attrValue,\r\n                            });\r\n                        }\r\n                        else if (attrName.startsWith(\"block-attribute-\")) {\r\n                            const idx = parseInt(attrName.slice(16), 10);\r\n                            info.push({\r\n                                type: \"attribute\",\r\n                                idx,\r\n                                name: attrValue,\r\n                                tag: tagName,\r\n                            });\r\n                        }\r\n                        else if (attrName.startsWith(\"block-property-\")) {\r\n                            const idx = parseInt(attrName.slice(15), 10);\r\n                            info.push({\r\n                                type: \"property\",\r\n                                idx,\r\n                                name: attrValue,\r\n                                tag: tagName,\r\n                            });\r\n                        }\r\n                        else if (attrName === \"block-attributes\") {\r\n                            info.push({\r\n                                type: \"attributes\",\r\n                                idx: parseInt(attrValue, 10),\r\n                            });\r\n                        }\r\n                        else if (attrName === \"block-ref\") {\r\n                            info.push({\r\n                                type: \"ref\",\r\n                                idx: parseInt(attrValue, 10),\r\n                            });\r\n                        }\r\n                        else {\r\n                            el.setAttribute(attrs[i].name, attrValue);\r\n                        }\r\n                    }\r\n                }\r\n                const tree = {\r\n                    parent,\r\n                    firstChild: null,\r\n                    nextSibling: null,\r\n                    el,\r\n                    info,\r\n                    refN: 0,\r\n                    currentNS,\r\n                };\r\n                if (node.firstChild) {\r\n                    const childNode = node.childNodes[0];\r\n                    if (node.childNodes.length === 1 &&\r\n                        childNode.nodeType === Node.ELEMENT_NODE &&\r\n                        childNode.tagName.startsWith(\"block-child-\")) {\r\n                        const tagName = childNode.tagName;\r\n                        const index = parseInt(tagName.slice(12), 10);\r\n                        info.push({ idx: index, type: \"child\", isOnlyChild: true });\r\n                    }\r\n                    else {\r\n                        tree.firstChild = buildTree(node.firstChild, tree, tree);\r\n                        el.appendChild(tree.firstChild.el);\r\n                        let curNode = node.firstChild;\r\n                        let curTree = tree.firstChild;\r\n                        while ((curNode = curNode.nextSibling)) {\r\n                            curTree.nextSibling = buildTree(curNode, curTree, tree);\r\n                            el.appendChild(curTree.nextSibling.el);\r\n                            curTree = curTree.nextSibling;\r\n                        }\r\n                    }\r\n                }\r\n                if (tree.info.length) {\r\n                    addRef(tree);\r\n                }\r\n                return tree;\r\n            }\r\n            case Node.TEXT_NODE:\r\n            case Node.COMMENT_NODE: {\r\n                // text node or comment node\r\n                const el = node.nodeType === Node.TEXT_NODE\r\n                    ? document.createTextNode(node.textContent)\r\n                    : document.createComment(node.textContent);\r\n                return {\r\n                    parent: parent,\r\n                    firstChild: null,\r\n                    nextSibling: null,\r\n                    el,\r\n                    info: [],\r\n                    refN: 0,\r\n                    currentNS: null,\r\n                };\r\n            }\r\n        }\r\n        throw new OwlError(\"boom\");\r\n    }\r\n    function addRef(tree) {\r\n        tree.isRef = true;\r\n        do {\r\n            tree.refN++;\r\n        } while ((tree = tree.parent));\r\n    }\r\n    function parentTree(tree) {\r\n        let parent = tree.parent;\r\n        while (parent && parent.nextSibling === tree) {\r\n            tree = parent;\r\n            parent = parent.parent;\r\n        }\r\n        return parent;\r\n    }\r\n    function buildContext(tree, ctx, fromIdx) {\r\n        if (!ctx) {\r\n            const children = new Array(tree.info.filter((v) => v.type === \"child\").length);\r\n            ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };\r\n            fromIdx = 0;\r\n        }\r\n        if (tree.refN) {\r\n            const initialIdx = fromIdx;\r\n            const isRef = tree.isRef;\r\n            const firstChild = tree.firstChild ? tree.firstChild.refN : 0;\r\n            const nextSibling = tree.nextSibling ? tree.nextSibling.refN : 0;\r\n            //node\r\n            if (isRef) {\r\n                for (let info of tree.info) {\r\n                    info.refIdx = initialIdx;\r\n                }\r\n                tree.refIdx = initialIdx;\r\n                updateCtx(ctx, tree);\r\n                fromIdx++;\r\n            }\r\n            // right\r\n            if (nextSibling) {\r\n                const idx = fromIdx + firstChild;\r\n                ctx.collectors.push({ idx, prevIdx: initialIdx, getVal: nodeGetNextSibling });\r\n                buildContext(tree.nextSibling, ctx, idx);\r\n            }\r\n            // left\r\n            if (firstChild) {\r\n                ctx.collectors.push({ idx: fromIdx, prevIdx: initialIdx, getVal: nodeGetFirstChild });\r\n                buildContext(tree.firstChild, ctx, fromIdx);\r\n            }\r\n        }\r\n        return ctx;\r\n    }\r\n    function updateCtx(ctx, tree) {\r\n        for (let info of tree.info) {\r\n            switch (info.type) {\r\n                case \"text\":\r\n                    ctx.locations.push({\r\n                        idx: info.idx,\r\n                        refIdx: info.refIdx,\r\n                        setData: setText,\r\n                        updateData: setText,\r\n                    });\r\n                    break;\r\n                case \"child\":\r\n                    if (info.isOnlyChild) {\r\n                        // tree is the parentnode here\r\n                        ctx.children[info.idx] = {\r\n                            parentRefIdx: info.refIdx,\r\n                            isOnlyChild: true,\r\n                        };\r\n                    }\r\n                    else {\r\n                        // tree is the anchor text node\r\n                        ctx.children[info.idx] = {\r\n                            parentRefIdx: parentTree(tree).refIdx,\r\n                            afterRefIdx: info.refIdx,\r\n                        };\r\n                    }\r\n                    break;\r\n                case \"property\": {\r\n                    const refIdx = info.refIdx;\r\n                    const setProp = makePropSetter(info.name);\r\n                    ctx.locations.push({\r\n                        idx: info.idx,\r\n                        refIdx,\r\n                        setData: setProp,\r\n                        updateData: setProp,\r\n                    });\r\n                    break;\r\n                }\r\n                case \"attribute\": {\r\n                    const refIdx = info.refIdx;\r\n                    let updater;\r\n                    let setter;\r\n                    if (info.name === \"class\") {\r\n                        setter = setClass;\r\n                        updater = updateClass;\r\n                    }\r\n                    else {\r\n                        setter = createAttrUpdater(info.name);\r\n                        updater = setter;\r\n                    }\r\n                    ctx.locations.push({\r\n                        idx: info.idx,\r\n                        refIdx,\r\n                        setData: setter,\r\n                        updateData: updater,\r\n                    });\r\n                    break;\r\n                }\r\n                case \"attributes\":\r\n                    ctx.locations.push({\r\n                        idx: info.idx,\r\n                        refIdx: info.refIdx,\r\n                        setData: attrsSetter,\r\n                        updateData: attrsUpdater,\r\n                    });\r\n                    break;\r\n                case \"handler\": {\r\n                    const { setup, update } = createEventHandler(info.event);\r\n                    ctx.locations.push({\r\n                        idx: info.idx,\r\n                        refIdx: info.refIdx,\r\n                        setData: setup,\r\n                        updateData: update,\r\n                    });\r\n                    break;\r\n                }\r\n                case \"ref\":\r\n                    const index = ctx.cbRefs.push(info.idx) - 1;\r\n                    ctx.locations.push({\r\n                        idx: info.idx,\r\n                        refIdx: info.refIdx,\r\n                        setData: makeRefSetter(index, ctx.refList),\r\n                        updateData: NO_OP,\r\n                    });\r\n            }\r\n        }\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // building the concrete block class\r\n    // -----------------------------------------------------------------------------\r\n    function buildBlock(template, ctx) {\r\n        let B = createBlockClass(template, ctx);\r\n        if (ctx.cbRefs.length) {\r\n            const cbRefs = ctx.cbRefs;\r\n            const refList = ctx.refList;\r\n            let cbRefsNumber = cbRefs.length;\r\n            B = class extends B {\r\n                mount(parent, afterNode) {\r\n                    refList.push(new Array(cbRefsNumber));\r\n                    super.mount(parent, afterNode);\r\n                    for (let cbRef of refList.pop()) {\r\n                        cbRef();\r\n                    }\r\n                }\r\n                remove() {\r\n                    super.remove();\r\n                    for (let cbRef of cbRefs) {\r\n                        let fn = this.data[cbRef];\r\n                        fn(null);\r\n                    }\r\n                }\r\n            };\r\n        }\r\n        if (ctx.children.length) {\r\n            B = class extends B {\r\n                constructor(data, children) {\r\n                    super(data);\r\n                    this.children = children;\r\n                }\r\n            };\r\n            B.prototype.beforeRemove = VMulti.prototype.beforeRemove;\r\n            return (data, children = []) => new B(data, children);\r\n        }\r\n        return (data) => new B(data);\r\n    }\r\n    function createBlockClass(template, ctx) {\r\n        const { refN, collectors, children } = ctx;\r\n        const colN = collectors.length;\r\n        ctx.locations.sort((a, b) => a.idx - b.idx);\r\n        const locations = ctx.locations.map((loc) => ({\r\n            refIdx: loc.refIdx,\r\n            setData: loc.setData,\r\n            updateData: loc.updateData,\r\n        }));\r\n        const locN = locations.length;\r\n        const childN = children.length;\r\n        const childrenLocs = children;\r\n        const isDynamic = refN > 0;\r\n        // these values are defined here to make them faster to lookup in the class\r\n        // block scope\r\n        const nodeCloneNode = nodeProto$2.cloneNode;\r\n        const nodeInsertBefore = nodeProto$2.insertBefore;\r\n        const elementRemove = elementProto.remove;\r\n        class Block {\r\n            constructor(data) {\r\n                this.data = data;\r\n            }\r\n            beforeRemove() { }\r\n            remove() {\r\n                elementRemove.call(this.el);\r\n            }\r\n            firstNode() {\r\n                return this.el;\r\n            }\r\n            moveBeforeDOMNode(node, parent = this.parentEl) {\r\n                this.parentEl = parent;\r\n                nodeInsertBefore.call(parent, this.el, node);\r\n            }\r\n            moveBeforeVNode(other, afterNode) {\r\n                nodeInsertBefore.call(this.parentEl, this.el, other ? other.el : afterNode);\r\n            }\r\n            toString() {\r\n                const div = document.createElement(\"div\");\r\n                this.mount(div, null);\r\n                return div.innerHTML;\r\n            }\r\n            mount(parent, afterNode) {\r\n                const el = nodeCloneNode.call(template, true);\r\n                nodeInsertBefore.call(parent, el, afterNode);\r\n                this.el = el;\r\n                this.parentEl = parent;\r\n            }\r\n            patch(other, withBeforeRemove) { }\r\n        }\r\n        if (isDynamic) {\r\n            Block.prototype.mount = function mount(parent, afterNode) {\r\n                const el = nodeCloneNode.call(template, true);\r\n                // collecting references\r\n                const refs = new Array(refN);\r\n                this.refs = refs;\r\n                refs[0] = el;\r\n                for (let i = 0; i < colN; i++) {\r\n                    const w = collectors[i];\r\n                    refs[w.idx] = w.getVal.call(refs[w.prevIdx]);\r\n                }\r\n                // applying data to all update points\r\n                if (locN) {\r\n                    const data = this.data;\r\n                    for (let i = 0; i < locN; i++) {\r\n                        const loc = locations[i];\r\n                        loc.setData.call(refs[loc.refIdx], data[i]);\r\n                    }\r\n                }\r\n                nodeInsertBefore.call(parent, el, afterNode);\r\n                // preparing all children\r\n                if (childN) {\r\n                    const children = this.children;\r\n                    for (let i = 0; i < childN; i++) {\r\n                        const child = children[i];\r\n                        if (child) {\r\n                            const loc = childrenLocs[i];\r\n                            const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;\r\n                            child.isOnlyChild = loc.isOnlyChild;\r\n                            child.mount(refs[loc.parentRefIdx], afterNode);\r\n                        }\r\n                    }\r\n                }\r\n                this.el = el;\r\n                this.parentEl = parent;\r\n            };\r\n            Block.prototype.patch = function patch(other, withBeforeRemove) {\r\n                if (this === other) {\r\n                    return;\r\n                }\r\n                const refs = this.refs;\r\n                // update texts/attributes/\r\n                if (locN) {\r\n                    const data1 = this.data;\r\n                    const data2 = other.data;\r\n                    for (let i = 0; i < locN; i++) {\r\n                        const val1 = data1[i];\r\n                        const val2 = data2[i];\r\n                        if (val1 !== val2) {\r\n                            const loc = locations[i];\r\n                            loc.updateData.call(refs[loc.refIdx], val2, val1);\r\n                        }\r\n                    }\r\n                    this.data = data2;\r\n                }\r\n                // update children\r\n                if (childN) {\r\n                    let children1 = this.children;\r\n                    const children2 = other.children;\r\n                    for (let i = 0; i < childN; i++) {\r\n                        const child1 = children1[i];\r\n                        const child2 = children2[i];\r\n                        if (child1) {\r\n                            if (child2) {\r\n                                child1.patch(child2, withBeforeRemove);\r\n                            }\r\n                            else {\r\n                                if (withBeforeRemove) {\r\n                                    child1.beforeRemove();\r\n                                }\r\n                                child1.remove();\r\n                                children1[i] = undefined;\r\n                            }\r\n                        }\r\n                        else if (child2) {\r\n                            const loc = childrenLocs[i];\r\n                            const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;\r\n                            child2.mount(refs[loc.parentRefIdx], afterNode);\r\n                            children1[i] = child2;\r\n                        }\r\n                    }\r\n                }\r\n            };\r\n        }\r\n        return Block;\r\n    }\r\n    function setText(value) {\r\n        characterDataSetData.call(this, toText(value));\r\n    }\r\n    function makeRefSetter(index, refs) {\r\n        return function setRef(fn) {\r\n            refs[refs.length - 1][index] = () => fn(this);\r\n        };\r\n    }\r\n\r\n    const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p);\r\n    const nodeProto$1 = Node.prototype;\r\n    const nodeInsertBefore$1 = nodeProto$1.insertBefore;\r\n    const nodeAppendChild = nodeProto$1.appendChild;\r\n    const nodeRemoveChild$1 = nodeProto$1.removeChild;\r\n    const nodeSetTextContent = getDescriptor(nodeProto$1, \"textContent\").set;\r\n    // -----------------------------------------------------------------------------\r\n    // List Node\r\n    // -----------------------------------------------------------------------------\r\n    class VList {\r\n        constructor(children) {\r\n            this.children = children;\r\n        }\r\n        mount(parent, afterNode) {\r\n            const children = this.children;\r\n            const _anchor = document.createTextNode(\"\");\r\n            this.anchor = _anchor;\r\n            nodeInsertBefore$1.call(parent, _anchor, afterNode);\r\n            const l = children.length;\r\n            if (l) {\r\n                const mount = children[0].mount;\r\n                for (let i = 0; i < l; i++) {\r\n                    mount.call(children[i], parent, _anchor);\r\n                }\r\n            }\r\n            this.parentEl = parent;\r\n        }\r\n        moveBeforeDOMNode(node, parent = this.parentEl) {\r\n            this.parentEl = parent;\r\n            const children = this.children;\r\n            for (let i = 0, l = children.length; i < l; i++) {\r\n                children[i].moveBeforeDOMNode(node, parent);\r\n            }\r\n            parent.insertBefore(this.anchor, node);\r\n        }\r\n        moveBeforeVNode(other, afterNode) {\r\n            if (other) {\r\n                const next = other.children[0];\r\n                afterNode = (next ? next.firstNode() : other.anchor) || null;\r\n            }\r\n            const children = this.children;\r\n            for (let i = 0, l = children.length; i < l; i++) {\r\n                children[i].moveBeforeVNode(null, afterNode);\r\n            }\r\n            this.parentEl.insertBefore(this.anchor, afterNode);\r\n        }\r\n        patch(other, withBeforeRemove) {\r\n            if (this === other) {\r\n                return;\r\n            }\r\n            const ch1 = this.children;\r\n            const ch2 = other.children;\r\n            if (ch2.length === 0 && ch1.length === 0) {\r\n                return;\r\n            }\r\n            this.children = ch2;\r\n            const proto = ch2[0] || ch1[0];\r\n            const { mount: cMount, patch: cPatch, remove: cRemove, beforeRemove, moveBeforeVNode: cMoveBefore, firstNode: cFirstNode, } = proto;\r\n            const _anchor = this.anchor;\r\n            const isOnlyChild = this.isOnlyChild;\r\n            const parent = this.parentEl;\r\n            // fast path: no new child => only remove\r\n            if (ch2.length === 0 && isOnlyChild) {\r\n                if (withBeforeRemove) {\r\n                    for (let i = 0, l = ch1.length; i < l; i++) {\r\n                        beforeRemove.call(ch1[i]);\r\n                    }\r\n                }\r\n                nodeSetTextContent.call(parent, \"\");\r\n                nodeAppendChild.call(parent, _anchor);\r\n                return;\r\n            }\r\n            let startIdx1 = 0;\r\n            let startIdx2 = 0;\r\n            let startVn1 = ch1[0];\r\n            let startVn2 = ch2[0];\r\n            let endIdx1 = ch1.length - 1;\r\n            let endIdx2 = ch2.length - 1;\r\n            let endVn1 = ch1[endIdx1];\r\n            let endVn2 = ch2[endIdx2];\r\n            let mapping = undefined;\r\n            while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {\r\n                // -------------------------------------------------------------------\r\n                if (startVn1 === null) {\r\n                    startVn1 = ch1[++startIdx1];\r\n                    continue;\r\n                }\r\n                // -------------------------------------------------------------------\r\n                if (endVn1 === null) {\r\n                    endVn1 = ch1[--endIdx1];\r\n                    continue;\r\n                }\r\n                // -------------------------------------------------------------------\r\n                let startKey1 = startVn1.key;\r\n                let startKey2 = startVn2.key;\r\n                if (startKey1 === startKey2) {\r\n                    cPatch.call(startVn1, startVn2, withBeforeRemove);\r\n                    ch2[startIdx2] = startVn1;\r\n                    startVn1 = ch1[++startIdx1];\r\n                    startVn2 = ch2[++startIdx2];\r\n                    continue;\r\n                }\r\n                // -------------------------------------------------------------------\r\n                let endKey1 = endVn1.key;\r\n                let endKey2 = endVn2.key;\r\n                if (endKey1 === endKey2) {\r\n                    cPatch.call(endVn1, endVn2, withBeforeRemove);\r\n                    ch2[endIdx2] = endVn1;\r\n                    endVn1 = ch1[--endIdx1];\r\n                    endVn2 = ch2[--endIdx2];\r\n                    continue;\r\n                }\r\n                // -------------------------------------------------------------------\r\n                if (startKey1 === endKey2) {\r\n                    // bnode moved right\r\n                    cPatch.call(startVn1, endVn2, withBeforeRemove);\r\n                    ch2[endIdx2] = startVn1;\r\n                    const nextChild = ch2[endIdx2 + 1];\r\n                    cMoveBefore.call(startVn1, nextChild, _anchor);\r\n                    startVn1 = ch1[++startIdx1];\r\n                    endVn2 = ch2[--endIdx2];\r\n                    continue;\r\n                }\r\n                // -------------------------------------------------------------------\r\n                if (endKey1 === startKey2) {\r\n                    // bnode moved left\r\n                    cPatch.call(endVn1, startVn2, withBeforeRemove);\r\n                    ch2[startIdx2] = endVn1;\r\n                    const nextChild = ch1[startIdx1];\r\n                    cMoveBefore.call(endVn1, nextChild, _anchor);\r\n                    endVn1 = ch1[--endIdx1];\r\n                    startVn2 = ch2[++startIdx2];\r\n                    continue;\r\n                }\r\n                // -------------------------------------------------------------------\r\n                mapping = mapping || createMapping(ch1, startIdx1, endIdx1);\r\n                let idxInOld = mapping[startKey2];\r\n                if (idxInOld === undefined) {\r\n                    cMount.call(startVn2, parent, cFirstNode.call(startVn1) || null);\r\n                }\r\n                else {\r\n                    const elmToMove = ch1[idxInOld];\r\n                    cMoveBefore.call(elmToMove, startVn1, null);\r\n                    cPatch.call(elmToMove, startVn2, withBeforeRemove);\r\n                    ch2[startIdx2] = elmToMove;\r\n                    ch1[idxInOld] = null;\r\n                }\r\n                startVn2 = ch2[++startIdx2];\r\n            }\r\n            // ---------------------------------------------------------------------\r\n            if (startIdx1 <= endIdx1 || startIdx2 <= endIdx2) {\r\n                if (startIdx1 > endIdx1) {\r\n                    const nextChild = ch2[endIdx2 + 1];\r\n                    const anchor = nextChild ? cFirstNode.call(nextChild) || null : _anchor;\r\n                    for (let i = startIdx2; i <= endIdx2; i++) {\r\n                        cMount.call(ch2[i], parent, anchor);\r\n                    }\r\n                }\r\n                else {\r\n                    for (let i = startIdx1; i <= endIdx1; i++) {\r\n                        let ch = ch1[i];\r\n                        if (ch) {\r\n                            if (withBeforeRemove) {\r\n                                beforeRemove.call(ch);\r\n                            }\r\n                            cRemove.call(ch);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        beforeRemove() {\r\n            const children = this.children;\r\n            const l = children.length;\r\n            if (l) {\r\n                const beforeRemove = children[0].beforeRemove;\r\n                for (let i = 0; i < l; i++) {\r\n                    beforeRemove.call(children[i]);\r\n                }\r\n            }\r\n        }\r\n        remove() {\r\n            const { parentEl, anchor } = this;\r\n            if (this.isOnlyChild) {\r\n                nodeSetTextContent.call(parentEl, \"\");\r\n            }\r\n            else {\r\n                const children = this.children;\r\n                const l = children.length;\r\n                if (l) {\r\n                    const remove = children[0].remove;\r\n                    for (let i = 0; i < l; i++) {\r\n                        remove.call(children[i]);\r\n                    }\r\n                }\r\n                nodeRemoveChild$1.call(parentEl, anchor);\r\n            }\r\n        }\r\n        firstNode() {\r\n            const child = this.children[0];\r\n            return child ? child.firstNode() : undefined;\r\n        }\r\n        toString() {\r\n            return this.children.map((c) => c.toString()).join(\"\");\r\n        }\r\n    }\r\n    function list(children) {\r\n        return new VList(children);\r\n    }\r\n    function createMapping(ch1, startIdx1, endIdx2) {\r\n        let mapping = {};\r\n        for (let i = startIdx1; i <= endIdx2; i++) {\r\n            mapping[ch1[i].key] = i;\r\n        }\r\n        return mapping;\r\n    }\r\n\r\n    const nodeProto = Node.prototype;\r\n    const nodeInsertBefore = nodeProto.insertBefore;\r\n    const nodeRemoveChild = nodeProto.removeChild;\r\n    class VHtml {\r\n        constructor(html) {\r\n            this.content = [];\r\n            this.html = html;\r\n        }\r\n        mount(parent, afterNode) {\r\n            this.parentEl = parent;\r\n            const template = document.createElement(\"template\");\r\n            template.innerHTML = this.html;\r\n            this.content = [...template.content.childNodes];\r\n            for (let elem of this.content) {\r\n                nodeInsertBefore.call(parent, elem, afterNode);\r\n            }\r\n            if (!this.content.length) {\r\n                const textNode = document.createTextNode(\"\");\r\n                this.content.push(textNode);\r\n                nodeInsertBefore.call(parent, textNode, afterNode);\r\n            }\r\n        }\r\n        moveBeforeDOMNode(node, parent = this.parentEl) {\r\n            this.parentEl = parent;\r\n            for (let elem of this.content) {\r\n                nodeInsertBefore.call(parent, elem, node);\r\n            }\r\n        }\r\n        moveBeforeVNode(other, afterNode) {\r\n            const target = other ? other.content[0] : afterNode;\r\n            this.moveBeforeDOMNode(target);\r\n        }\r\n        patch(other) {\r\n            if (this === other) {\r\n                return;\r\n            }\r\n            const html2 = other.html;\r\n            if (this.html !== html2) {\r\n                const parent = this.parentEl;\r\n                // insert new html in front of current\r\n                const afterNode = this.content[0];\r\n                const template = document.createElement(\"template\");\r\n                template.innerHTML = html2;\r\n                const content = [...template.content.childNodes];\r\n                for (let elem of content) {\r\n                    nodeInsertBefore.call(parent, elem, afterNode);\r\n                }\r\n                if (!content.length) {\r\n                    const textNode = document.createTextNode(\"\");\r\n                    content.push(textNode);\r\n                    nodeInsertBefore.call(parent, textNode, afterNode);\r\n                }\r\n                // remove current content\r\n                this.remove();\r\n                this.content = content;\r\n                this.html = other.html;\r\n            }\r\n        }\r\n        beforeRemove() { }\r\n        remove() {\r\n            const parent = this.parentEl;\r\n            for (let elem of this.content) {\r\n                nodeRemoveChild.call(parent, elem);\r\n            }\r\n        }\r\n        firstNode() {\r\n            return this.content[0];\r\n        }\r\n        toString() {\r\n            return this.html;\r\n        }\r\n    }\r\n    function html(str) {\r\n        return new VHtml(str);\r\n    }\r\n\r\n    function createCatcher(eventsSpec) {\r\n        const n = Object.keys(eventsSpec).length;\r\n        class VCatcher {\r\n            constructor(child, handlers) {\r\n                this.handlerFns = [];\r\n                this.afterNode = null;\r\n                this.child = child;\r\n                this.handlerData = handlers;\r\n            }\r\n            mount(parent, afterNode) {\r\n                this.parentEl = parent;\r\n                this.child.mount(parent, afterNode);\r\n                this.afterNode = document.createTextNode(\"\");\r\n                parent.insertBefore(this.afterNode, afterNode);\r\n                this.wrapHandlerData();\r\n                for (let name in eventsSpec) {\r\n                    const index = eventsSpec[name];\r\n                    const handler = createEventHandler(name);\r\n                    this.handlerFns[index] = handler;\r\n                    handler.setup.call(parent, this.handlerData[index]);\r\n                }\r\n            }\r\n            wrapHandlerData() {\r\n                for (let i = 0; i < n; i++) {\r\n                    let handler = this.handlerData[i];\r\n                    // handler = [...mods, fn, comp], so we need to replace second to last elem\r\n                    let idx = handler.length - 2;\r\n                    let origFn = handler[idx];\r\n                    const self = this;\r\n                    handler[idx] = function (ev) {\r\n                        const target = ev.target;\r\n                        let currentNode = self.child.firstNode();\r\n                        const afterNode = self.afterNode;\r\n                        while (currentNode && currentNode !== afterNode) {\r\n                            if (currentNode.contains(target)) {\r\n                                return origFn.call(this, ev);\r\n                            }\r\n                            currentNode = currentNode.nextSibling;\r\n                        }\r\n                    };\r\n                }\r\n            }\r\n            moveBeforeDOMNode(node, parent = this.parentEl) {\r\n                this.parentEl = parent;\r\n                this.child.moveBeforeDOMNode(node, parent);\r\n                parent.insertBefore(this.afterNode, node);\r\n            }\r\n            moveBeforeVNode(other, afterNode) {\r\n                if (other) {\r\n                    // check this with @ged-odoo for use in foreach\r\n                    afterNode = other.firstNode() || afterNode;\r\n                }\r\n                this.child.moveBeforeVNode(other ? other.child : null, afterNode);\r\n                this.parentEl.insertBefore(this.afterNode, afterNode);\r\n            }\r\n            patch(other, withBeforeRemove) {\r\n                if (this === other) {\r\n                    return;\r\n                }\r\n                this.handlerData = other.handlerData;\r\n                this.wrapHandlerData();\r\n                for (let i = 0; i < n; i++) {\r\n                    this.handlerFns[i].update.call(this.parentEl, this.handlerData[i]);\r\n                }\r\n                this.child.patch(other.child, withBeforeRemove);\r\n            }\r\n            beforeRemove() {\r\n                this.child.beforeRemove();\r\n            }\r\n            remove() {\r\n                for (let i = 0; i < n; i++) {\r\n                    this.handlerFns[i].remove.call(this.parentEl);\r\n                }\r\n                this.child.remove();\r\n                this.afterNode.remove();\r\n            }\r\n            firstNode() {\r\n                return this.child.firstNode();\r\n            }\r\n            toString() {\r\n                return this.child.toString();\r\n            }\r\n        }\r\n        return function (child, handlers) {\r\n            return new VCatcher(child, handlers);\r\n        };\r\n    }\r\n\r\n    function mount$1(vnode, fixture, afterNode = null) {\r\n        vnode.mount(fixture, afterNode);\r\n    }\r\n    function patch(vnode1, vnode2, withBeforeRemove = false) {\r\n        vnode1.patch(vnode2, withBeforeRemove);\r\n    }\r\n    function remove(vnode, withBeforeRemove = false) {\r\n        if (withBeforeRemove) {\r\n            vnode.beforeRemove();\r\n        }\r\n        vnode.remove();\r\n    }\r\n\r\n    // Maps fibers to thrown errors\r\n    const fibersInError = new WeakMap();\r\n    const nodeErrorHandlers = new WeakMap();\r\n    function _handleError(node, error) {\r\n        if (!node) {\r\n            return false;\r\n        }\r\n        const fiber = node.fiber;\r\n        if (fiber) {\r\n            fibersInError.set(fiber, error);\r\n        }\r\n        const errorHandlers = nodeErrorHandlers.get(node);\r\n        if (errorHandlers) {\r\n            let handled = false;\r\n            // execute in the opposite order\r\n            for (let i = errorHandlers.length - 1; i >= 0; i--) {\r\n                try {\r\n                    errorHandlers[i](error);\r\n                    handled = true;\r\n                    break;\r\n                }\r\n                catch (e) {\r\n                    error = e;\r\n                }\r\n            }\r\n            if (handled) {\r\n                return true;\r\n            }\r\n        }\r\n        return _handleError(node.parent, error);\r\n    }\r\n    function handleError(params) {\r\n        let { error } = params;\r\n        // Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)\r\n        if (!(error instanceof OwlError)) {\r\n            error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's \"cause\" property)`), { cause: error });\r\n        }\r\n        const node = \"node\" in params ? params.node : params.fiber.node;\r\n        const fiber = \"fiber\" in params ? params.fiber : node.fiber;\r\n        if (fiber) {\r\n            // resets the fibers on components if possible. This is important so that\r\n            // new renderings can be properly included in the initial one, if any.\r\n            let current = fiber;\r\n            do {\r\n                current.node.fiber = current;\r\n                current = current.parent;\r\n            } while (current);\r\n            fibersInError.set(fiber.root, error);\r\n        }\r\n        const handled = _handleError(node, error);\r\n        if (!handled) {\r\n            console.warn(`[Owl] Unhandled error. Destroying the root component`);\r\n            try {\r\n                node.app.destroy();\r\n            }\r\n            catch (e) {\r\n                console.error(e);\r\n            }\r\n            throw error;\r\n        }\r\n    }\r\n\r\n    function makeChildFiber(node, parent) {\r\n        let current = node.fiber;\r\n        if (current) {\r\n            cancelFibers(current.children);\r\n            current.root = null;\r\n        }\r\n        return new Fiber(node, parent);\r\n    }\r\n    function makeRootFiber(node) {\r\n        let current = node.fiber;\r\n        if (current) {\r\n            let root = current.root;\r\n            // lock root fiber because canceling children fibers may destroy components,\r\n            // which means any arbitrary code can be run in onWillDestroy, which may\r\n            // trigger new renderings\r\n            root.locked = true;\r\n            root.setCounter(root.counter + 1 - cancelFibers(current.children));\r\n            root.locked = false;\r\n            current.children = [];\r\n            current.childrenMap = {};\r\n            current.bdom = null;\r\n            if (fibersInError.has(current)) {\r\n                fibersInError.delete(current);\r\n                fibersInError.delete(root);\r\n                current.appliedToDom = false;\r\n                if (current instanceof RootFiber) {\r\n                    // it is possible that this fiber is a fiber that crashed while being\r\n                    // mounted, so the mounted list is possibly corrupted. We restore it to\r\n                    // its normal initial state (which is empty list or a list with a mount\r\n                    // fiber.\r\n                    current.mounted = current instanceof MountFiber ? [current] : [];\r\n                }\r\n            }\r\n            return current;\r\n        }\r\n        const fiber = new RootFiber(node, null);\r\n        if (node.willPatch.length) {\r\n            fiber.willPatch.push(fiber);\r\n        }\r\n        if (node.patched.length) {\r\n            fiber.patched.push(fiber);\r\n        }\r\n        return fiber;\r\n    }\r\n    function throwOnRender() {\r\n        throw new OwlError(\"Attempted to render cancelled fiber\");\r\n    }\r\n    /**\r\n     * @returns number of not-yet rendered fibers cancelled\r\n     */\r\n    function cancelFibers(fibers) {\r\n        let result = 0;\r\n        for (let fiber of fibers) {\r\n            let node = fiber.node;\r\n            fiber.render = throwOnRender;\r\n            if (node.status === 0 /* NEW */) {\r\n                node.cancel();\r\n            }\r\n            node.fiber = null;\r\n            if (fiber.bdom) {\r\n                // if fiber has been rendered, this means that the component props have\r\n                // been updated. however, this fiber will not be patched to the dom, so\r\n                // it could happen that the next render compare the current props with\r\n                // the same props, and skip the render completely. With the next line,\r\n                // we kindly request the component code to force a render, so it works as\r\n                // expected.\r\n                node.forceNextRender = true;\r\n            }\r\n            else {\r\n                result++;\r\n            }\r\n            result += cancelFibers(fiber.children);\r\n        }\r\n        return result;\r\n    }\r\n    class Fiber {\r\n        constructor(node, parent) {\r\n            this.bdom = null;\r\n            this.children = [];\r\n            this.appliedToDom = false;\r\n            this.deep = false;\r\n            this.childrenMap = {};\r\n            this.node = node;\r\n            this.parent = parent;\r\n            if (parent) {\r\n                this.deep = parent.deep;\r\n                const root = parent.root;\r\n                root.setCounter(root.counter + 1);\r\n                this.root = root;\r\n                parent.children.push(this);\r\n            }\r\n            else {\r\n                this.root = this;\r\n            }\r\n        }\r\n        render() {\r\n            // if some parent has a fiber => register in followup\r\n            let prev = this.root.node;\r\n            let scheduler = prev.app.scheduler;\r\n            let current = prev.parent;\r\n            while (current) {\r\n                if (current.fiber) {\r\n                    let root = current.fiber.root;\r\n                    if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {\r\n                        current = root.node;\r\n                    }\r\n                    else {\r\n                        scheduler.delayedRenders.push(this);\r\n                        return;\r\n                    }\r\n                }\r\n                prev = current;\r\n                current = current.parent;\r\n            }\r\n            // there are no current rendering from above => we can render\r\n            this._render();\r\n        }\r\n        _render() {\r\n            const node = this.node;\r\n            const root = this.root;\r\n            if (root) {\r\n                try {\r\n                    this.bdom = true;\r\n                    this.bdom = node.renderFn();\r\n                }\r\n                catch (e) {\r\n                    node.app.handleError({ node, error: e });\r\n                }\r\n                root.setCounter(root.counter - 1);\r\n            }\r\n        }\r\n    }\r\n    class RootFiber extends Fiber {\r\n        constructor() {\r\n            super(...arguments);\r\n            this.counter = 1;\r\n            // only add stuff in this if they have registered some hooks\r\n            this.willPatch = [];\r\n            this.patched = [];\r\n            this.mounted = [];\r\n            // A fiber is typically locked when it is completing and the patch has not, or is being applied.\r\n            // i.e.: render triggered in onWillUnmount or in willPatch will be delayed\r\n            this.locked = false;\r\n        }\r\n        complete() {\r\n            const node = this.node;\r\n            this.locked = true;\r\n            let current = undefined;\r\n            let mountedFibers = this.mounted;\r\n            try {\r\n                // Step 1: calling all willPatch lifecycle hooks\r\n                for (current of this.willPatch) {\r\n                    // because of the asynchronous nature of the rendering, some parts of the\r\n                    // UI may have been rendered, then deleted in a followup rendering, and we\r\n                    // do not want to call onWillPatch in that case.\r\n                    let node = current.node;\r\n                    if (node.fiber === current) {\r\n                        const component = node.component;\r\n                        for (let cb of node.willPatch) {\r\n                            cb.call(component);\r\n                        }\r\n                    }\r\n                }\r\n                current = undefined;\r\n                // Step 2: patching the dom\r\n                node._patch();\r\n                this.locked = false;\r\n                // Step 4: calling all mounted lifecycle hooks\r\n                while ((current = mountedFibers.pop())) {\r\n                    current = current;\r\n                    if (current.appliedToDom) {\r\n                        for (let cb of current.node.mounted) {\r\n                            cb();\r\n                        }\r\n                    }\r\n                }\r\n                // Step 5: calling all patched hooks\r\n                let patchedFibers = this.patched;\r\n                while ((current = patchedFibers.pop())) {\r\n                    current = current;\r\n                    if (current.appliedToDom) {\r\n                        for (let cb of current.node.patched) {\r\n                            cb();\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            catch (e) {\r\n                // if mountedFibers is not empty, this means that a crash occured while\r\n                // calling the mounted hooks of some component. So, there may still be\r\n                // some component that have been mounted, but for which the mounted hooks\r\n                // have not been called. Here, we remove the willUnmount hooks for these\r\n                // specific component to prevent a worse situation (willUnmount being\r\n                // called even though mounted has not been called)\r\n                for (let fiber of mountedFibers) {\r\n                    fiber.node.willUnmount = [];\r\n                }\r\n                this.locked = false;\r\n                node.app.handleError({ fiber: current || this, error: e });\r\n            }\r\n        }\r\n        setCounter(newValue) {\r\n            this.counter = newValue;\r\n            if (newValue === 0) {\r\n                this.node.app.scheduler.flush();\r\n            }\r\n        }\r\n    }\r\n    class MountFiber extends RootFiber {\r\n        constructor(node, target, options = {}) {\r\n            super(node, null);\r\n            this.target = target;\r\n            this.position = options.position || \"last-child\";\r\n        }\r\n        complete() {\r\n            let current = this;\r\n            try {\r\n                const node = this.node;\r\n                node.children = this.childrenMap;\r\n                node.app.constructor.validateTarget(this.target);\r\n                if (node.bdom) {\r\n                    // this is a complicated situation: if we mount a fiber with an existing\r\n                    // bdom, this means that this same fiber was already completed, mounted,\r\n                    // but a crash occurred in some mounted hook. Then, it was handled and\r\n                    // the new rendering is being applied.\r\n                    node.updateDom();\r\n                }\r\n                else {\r\n                    node.bdom = this.bdom;\r\n                    if (this.position === \"last-child\" || this.target.childNodes.length === 0) {\r\n                        mount$1(node.bdom, this.target);\r\n                    }\r\n                    else {\r\n                        const firstChild = this.target.childNodes[0];\r\n                        mount$1(node.bdom, this.target, firstChild);\r\n                    }\r\n                }\r\n                // unregistering the fiber before mounted since it can do another render\r\n                // and that the current rendering is obviously completed\r\n                node.fiber = null;\r\n                node.status = 1 /* MOUNTED */;\r\n                this.appliedToDom = true;\r\n                let mountedFibers = this.mounted;\r\n                while ((current = mountedFibers.pop())) {\r\n                    if (current.appliedToDom) {\r\n                        for (let cb of current.node.mounted) {\r\n                            cb();\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            catch (e) {\r\n                this.node.app.handleError({ fiber: current, error: e });\r\n            }\r\n        }\r\n    }\r\n\r\n    // Special key to subscribe to, to be notified of key creation/deletion\r\n    const KEYCHANGES = Symbol(\"Key changes\");\r\n    // Used to specify the absence of a callback, can be used as WeakMap key but\r\n    // should only be used as a sentinel value and never called.\r\n    const NO_CALLBACK = () => {\r\n        throw new Error(\"Called NO_CALLBACK. Owl is broken, please report this to the maintainers.\");\r\n    };\r\n    const objectToString = Object.prototype.toString;\r\n    const objectHasOwnProperty = Object.prototype.hasOwnProperty;\r\n    // Use arrays because Array.includes is faster than Set.has for small arrays\r\n    const SUPPORTED_RAW_TYPES = [\"Object\", \"Array\", \"Set\", \"Map\", \"WeakMap\"];\r\n    const COLLECTION_RAW_TYPES = [\"Set\", \"Map\", \"WeakMap\"];\r\n    /**\r\n     * extract \"RawType\" from strings like \"[object RawType]\" => this lets us ignore\r\n     * many native objects such as Promise (whose toString is [object Promise])\r\n     * or Date ([object Date]), while also supporting collections without using\r\n     * instanceof in a loop\r\n     *\r\n     * @param obj the object to check\r\n     * @returns the raw type of the object\r\n     */\r\n    function rawType(obj) {\r\n        return objectToString.call(toRaw(obj)).slice(8, -1);\r\n    }\r\n    /**\r\n     * Checks whether a given value can be made into a reactive object.\r\n     *\r\n     * @param value the value to check\r\n     * @returns whether the value can be made reactive\r\n     */\r\n    function canBeMadeReactive(value) {\r\n        if (typeof value !== \"object\") {\r\n            return false;\r\n        }\r\n        return SUPPORTED_RAW_TYPES.includes(rawType(value));\r\n    }\r\n    /**\r\n     * Creates a reactive from the given object/callback if possible and returns it,\r\n     * returns the original object otherwise.\r\n     *\r\n     * @param value the value make reactive\r\n     * @returns a reactive for the given object when possible, the original otherwise\r\n     */\r\n    function possiblyReactive(val, cb) {\r\n        return canBeMadeReactive(val) ? reactive(val, cb) : val;\r\n    }\r\n    const skipped = new WeakSet();\r\n    /**\r\n     * Mark an object or array so that it is ignored by the reactivity system\r\n     *\r\n     * @param value the value to mark\r\n     * @returns the object itself\r\n     */\r\n    function markRaw(value) {\r\n        skipped.add(value);\r\n        return value;\r\n    }\r\n    /**\r\n     * Given a reactive objet, return the raw (non reactive) underlying object\r\n     *\r\n     * @param value a reactive value\r\n     * @returns the underlying value\r\n     */\r\n    function toRaw(value) {\r\n        return targets.has(value) ? targets.get(value) : value;\r\n    }\r\n    const targetToKeysToCallbacks = new WeakMap();\r\n    /**\r\n     * Observes a given key on a target with an callback. The callback will be\r\n     * called when the given key changes on the target.\r\n     *\r\n     * @param target the target whose key should be observed\r\n     * @param key the key to observe (or Symbol(KEYCHANGES) for key creation\r\n     *  or deletion)\r\n     * @param callback the function to call when the key changes\r\n     */\r\n    function observeTargetKey(target, key, callback) {\r\n        if (callback === NO_CALLBACK) {\r\n            return;\r\n        }\r\n        if (!targetToKeysToCallbacks.get(target)) {\r\n            targetToKeysToCallbacks.set(target, new Map());\r\n        }\r\n        const keyToCallbacks = targetToKeysToCallbacks.get(target);\r\n        if (!keyToCallbacks.get(key)) {\r\n            keyToCallbacks.set(key, new Set());\r\n        }\r\n        keyToCallbacks.get(key).add(callback);\r\n        if (!callbacksToTargets.has(callback)) {\r\n            callbacksToTargets.set(callback, new Set());\r\n        }\r\n        callbacksToTargets.get(callback).add(target);\r\n    }\r\n    /**\r\n     * Notify Reactives that are observing a given target that a key has changed on\r\n     * the target.\r\n     *\r\n     * @param target target whose Reactives should be notified that the target was\r\n     *  changed.\r\n     * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created\r\n     *   or deleted)\r\n     */\r\n    function notifyReactives(target, key) {\r\n        const keyToCallbacks = targetToKeysToCallbacks.get(target);\r\n        if (!keyToCallbacks) {\r\n            return;\r\n        }\r\n        const callbacks = keyToCallbacks.get(key);\r\n        if (!callbacks) {\r\n            return;\r\n        }\r\n        // Loop on copy because clearReactivesForCallback will modify the set in place\r\n        for (const callback of [...callbacks]) {\r\n            clearReactivesForCallback(callback);\r\n            callback();\r\n        }\r\n    }\r\n    const callbacksToTargets = new WeakMap();\r\n    /**\r\n     * Clears all subscriptions of the Reactives associated with a given callback.\r\n     *\r\n     * @param callback the callback for which the reactives need to be cleared\r\n     */\r\n    function clearReactivesForCallback(callback) {\r\n        const targetsToClear = callbacksToTargets.get(callback);\r\n        if (!targetsToClear) {\r\n            return;\r\n        }\r\n        for (const target of targetsToClear) {\r\n            const observedKeys = targetToKeysToCallbacks.get(target);\r\n            if (!observedKeys) {\r\n                continue;\r\n            }\r\n            for (const [key, callbacks] of observedKeys.entries()) {\r\n                callbacks.delete(callback);\r\n                if (!callbacks.size) {\r\n                    observedKeys.delete(key);\r\n                }\r\n            }\r\n        }\r\n        targetsToClear.clear();\r\n    }\r\n    function getSubscriptions(callback) {\r\n        const targets = callbacksToTargets.get(callback) || [];\r\n        return [...targets].map((target) => {\r\n            const keysToCallbacks = targetToKeysToCallbacks.get(target);\r\n            let keys = [];\r\n            if (keysToCallbacks) {\r\n                for (const [key, cbs] of keysToCallbacks) {\r\n                    if (cbs.has(callback)) {\r\n                        keys.push(key);\r\n                    }\r\n                }\r\n            }\r\n            return { target, keys };\r\n        });\r\n    }\r\n    // Maps reactive objects to the underlying target\r\n    const targets = new WeakMap();\r\n    const reactiveCache = new WeakMap();\r\n    /**\r\n     * Creates a reactive proxy for an object. Reading data on the reactive object\r\n     * subscribes to changes to the data. Writing data on the object will cause the\r\n     * notify callback to be called if there are suscriptions to that data. Nested\r\n     * objects and arrays are automatically made reactive as well.\r\n     *\r\n     * Whenever you are notified of a change, all subscriptions are cleared, and if\r\n     * you would like to be notified of any further changes, you should go read\r\n     * the underlying data again. We assume that if you don't go read it again after\r\n     * being notified, it means that you are no longer interested in that data.\r\n     *\r\n     * Subscriptions:\r\n     * + Reading a property on an object will subscribe you to changes in the value\r\n     *    of that property.\r\n     * + Accessing an object's keys (eg with Object.keys or with `for..in`) will\r\n     *    subscribe you to the creation/deletion of keys. Checking the presence of a\r\n     *    key on the object with 'in' has the same effect.\r\n     * - getOwnPropertyDescriptor does not currently subscribe you to the property.\r\n     *    This is a choice that was made because changing a key's value will trigger\r\n     *    this trap and we do not want to subscribe by writes. This also means that\r\n     *    Object.hasOwnProperty doesn't subscribe as it goes through this trap.\r\n     *\r\n     * @param target the object for which to create a reactive proxy\r\n     * @param callback the function to call when an observed property of the\r\n     *  reactive has changed\r\n     * @returns a proxy that tracks changes to it\r\n     */\r\n    function reactive(target, callback = NO_CALLBACK) {\r\n        if (!canBeMadeReactive(target)) {\r\n            throw new OwlError(`Cannot make the given value reactive`);\r\n        }\r\n        if (skipped.has(target)) {\r\n            return target;\r\n        }\r\n        if (targets.has(target)) {\r\n            // target is reactive, create a reactive on the underlying object instead\r\n            return reactive(targets.get(target), callback);\r\n        }\r\n        if (!reactiveCache.has(target)) {\r\n            reactiveCache.set(target, new WeakMap());\r\n        }\r\n        const reactivesForTarget = reactiveCache.get(target);\r\n        if (!reactivesForTarget.has(callback)) {\r\n            const targetRawType = rawType(target);\r\n            const handler = COLLECTION_RAW_TYPES.includes(targetRawType)\r\n                ? collectionsProxyHandler(target, callback, targetRawType)\r\n                : basicProxyHandler(callback);\r\n            const proxy = new Proxy(target, handler);\r\n            reactivesForTarget.set(callback, proxy);\r\n            targets.set(proxy, target);\r\n        }\r\n        return reactivesForTarget.get(callback);\r\n    }\r\n    /**\r\n     * Creates a basic proxy handler for regular objects and arrays.\r\n     *\r\n     * @param callback @see reactive\r\n     * @returns a proxy handler object\r\n     */\r\n    function basicProxyHandler(callback) {\r\n        return {\r\n            get(target, key, receiver) {\r\n                // non-writable non-configurable properties cannot be made reactive\r\n                const desc = Object.getOwnPropertyDescriptor(target, key);\r\n                if (desc && !desc.writable && !desc.configurable) {\r\n                    return Reflect.get(target, key, receiver);\r\n                }\r\n                observeTargetKey(target, key, callback);\r\n                return possiblyReactive(Reflect.get(target, key, receiver), callback);\r\n            },\r\n            set(target, key, value, receiver) {\r\n                const hadKey = objectHasOwnProperty.call(target, key);\r\n                const originalValue = Reflect.get(target, key, receiver);\r\n                const ret = Reflect.set(target, key, toRaw(value), receiver);\r\n                if (!hadKey && objectHasOwnProperty.call(target, key)) {\r\n                    notifyReactives(target, KEYCHANGES);\r\n                }\r\n                // While Array length may trigger the set trap, it's not actually set by this\r\n                // method but is updated behind the scenes, and the trap is not called with the\r\n                // new value. We disable the \"same-value-optimization\" for it because of that.\r\n                if (originalValue !== Reflect.get(target, key, receiver) ||\r\n                    (key === \"length\" && Array.isArray(target))) {\r\n                    notifyReactives(target, key);\r\n                }\r\n                return ret;\r\n            },\r\n            deleteProperty(target, key) {\r\n                const ret = Reflect.deleteProperty(target, key);\r\n                // TODO: only notify when something was actually deleted\r\n                notifyReactives(target, KEYCHANGES);\r\n                notifyReactives(target, key);\r\n                return ret;\r\n            },\r\n            ownKeys(target) {\r\n                observeTargetKey(target, KEYCHANGES, callback);\r\n                return Reflect.ownKeys(target);\r\n            },\r\n            has(target, key) {\r\n                // TODO: this observes all key changes instead of only the presence of the argument key\r\n                // observing the key itself would observe value changes instead of presence changes\r\n                // so we may need a finer grained system to distinguish observing value vs presence.\r\n                observeTargetKey(target, KEYCHANGES, callback);\r\n                return Reflect.has(target, key);\r\n            },\r\n        };\r\n    }\r\n    /**\r\n     * Creates a function that will observe the key that is passed to it when called\r\n     * and delegates to the underlying method.\r\n     *\r\n     * @param methodName name of the method to delegate to\r\n     * @param target @see reactive\r\n     * @param callback @see reactive\r\n     */\r\n    function makeKeyObserver(methodName, target, callback) {\r\n        return (key) => {\r\n            key = toRaw(key);\r\n            observeTargetKey(target, key, callback);\r\n            return possiblyReactive(target[methodName](key), callback);\r\n        };\r\n    }\r\n    /**\r\n     * Creates an iterable that will delegate to the underlying iteration method and\r\n     * observe keys as necessary.\r\n     *\r\n     * @param methodName name of the method to delegate to\r\n     * @param target @see reactive\r\n     * @param callback @see reactive\r\n     */\r\n    function makeIteratorObserver(methodName, target, callback) {\r\n        return function* () {\r\n            observeTargetKey(target, KEYCHANGES, callback);\r\n            const keys = target.keys();\r\n            for (const item of target[methodName]()) {\r\n                const key = keys.next().value;\r\n                observeTargetKey(target, key, callback);\r\n                yield possiblyReactive(item, callback);\r\n            }\r\n        };\r\n    }\r\n    /**\r\n     * Creates a forEach function that will delegate to forEach on the underlying\r\n     * collection while observing key changes, and keys as they're iterated over,\r\n     * and making the passed keys/values reactive.\r\n     *\r\n     * @param target @see reactive\r\n     * @param callback @see reactive\r\n     */\r\n    function makeForEachObserver(target, callback) {\r\n        return function forEach(forEachCb, thisArg) {\r\n            observeTargetKey(target, KEYCHANGES, callback);\r\n            target.forEach(function (val, key, targetObj) {\r\n                observeTargetKey(target, key, callback);\r\n                forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));\r\n            }, thisArg);\r\n        };\r\n    }\r\n    /**\r\n     * Creates a function that will delegate to an underlying method, and check if\r\n     * that method has modified the presence or value of a key, and notify the\r\n     * reactives appropriately.\r\n     *\r\n     * @param setterName name of the method to delegate to\r\n     * @param getterName name of the method which should be used to retrieve the\r\n     *  value before calling the delegate method for comparison purposes\r\n     * @param target @see reactive\r\n     */\r\n    function delegateAndNotify(setterName, getterName, target) {\r\n        return (key, value) => {\r\n            key = toRaw(key);\r\n            const hadKey = target.has(key);\r\n            const originalValue = target[getterName](key);\r\n            const ret = target[setterName](key, value);\r\n            const hasKey = target.has(key);\r\n            if (hadKey !== hasKey) {\r\n                notifyReactives(target, KEYCHANGES);\r\n            }\r\n            if (originalValue !== target[getterName](key)) {\r\n                notifyReactives(target, key);\r\n            }\r\n            return ret;\r\n        };\r\n    }\r\n    /**\r\n     * Creates a function that will clear the underlying collection and notify that\r\n     * the keys of the collection have changed.\r\n     *\r\n     * @param target @see reactive\r\n     */\r\n    function makeClearNotifier(target) {\r\n        return () => {\r\n            const allKeys = [...target.keys()];\r\n            target.clear();\r\n            notifyReactives(target, KEYCHANGES);\r\n            for (const key of allKeys) {\r\n                notifyReactives(target, key);\r\n            }\r\n        };\r\n    }\r\n    /**\r\n     * Maps raw type of an object to an object containing functions that can be used\r\n     * to build an appropritate proxy handler for that raw type. Eg: when making a\r\n     * reactive set, calling the has method should mark the key that is being\r\n     * retrieved as observed, and calling the add or delete method should notify the\r\n     * reactives that the key which is being added or deleted has been modified.\r\n     */\r\n    const rawTypeToFuncHandlers = {\r\n        Set: (target, callback) => ({\r\n            has: makeKeyObserver(\"has\", target, callback),\r\n            add: delegateAndNotify(\"add\", \"has\", target),\r\n            delete: delegateAndNotify(\"delete\", \"has\", target),\r\n            keys: makeIteratorObserver(\"keys\", target, callback),\r\n            values: makeIteratorObserver(\"values\", target, callback),\r\n            entries: makeIteratorObserver(\"entries\", target, callback),\r\n            [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),\r\n            forEach: makeForEachObserver(target, callback),\r\n            clear: makeClearNotifier(target),\r\n            get size() {\r\n                observeTargetKey(target, KEYCHANGES, callback);\r\n                return target.size;\r\n            },\r\n        }),\r\n        Map: (target, callback) => ({\r\n            has: makeKeyObserver(\"has\", target, callback),\r\n            get: makeKeyObserver(\"get\", target, callback),\r\n            set: delegateAndNotify(\"set\", \"get\", target),\r\n            delete: delegateAndNotify(\"delete\", \"has\", target),\r\n            keys: makeIteratorObserver(\"keys\", target, callback),\r\n            values: makeIteratorObserver(\"values\", target, callback),\r\n            entries: makeIteratorObserver(\"entries\", target, callback),\r\n            [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),\r\n            forEach: makeForEachObserver(target, callback),\r\n            clear: makeClearNotifier(target),\r\n            get size() {\r\n                observeTargetKey(target, KEYCHANGES, callback);\r\n                return target.size;\r\n            },\r\n        }),\r\n        WeakMap: (target, callback) => ({\r\n            has: makeKeyObserver(\"has\", target, callback),\r\n            get: makeKeyObserver(\"get\", target, callback),\r\n            set: delegateAndNotify(\"set\", \"get\", target),\r\n            delete: delegateAndNotify(\"delete\", \"has\", target),\r\n        }),\r\n    };\r\n    /**\r\n     * Creates a proxy handler for collections (Set/Map/WeakMap)\r\n     *\r\n     * @param callback @see reactive\r\n     * @param target @see reactive\r\n     * @returns a proxy handler object\r\n     */\r\n    function collectionsProxyHandler(target, callback, targetRawType) {\r\n        // TODO: if performance is an issue we can create the special handlers lazily when each\r\n        // property is read.\r\n        const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);\r\n        return Object.assign(basicProxyHandler(callback), {\r\n            // FIXME: probably broken when part of prototype chain since we ignore the receiver\r\n            get(target, key) {\r\n                if (objectHasOwnProperty.call(specialHandlers, key)) {\r\n                    return specialHandlers[key];\r\n                }\r\n                observeTargetKey(target, key, callback);\r\n                return possiblyReactive(target[key], callback);\r\n            },\r\n        });\r\n    }\r\n\r\n    let currentNode = null;\r\n    function saveCurrent() {\r\n        let n = currentNode;\r\n        return () => {\r\n            currentNode = n;\r\n        };\r\n    }\r\n    function getCurrent() {\r\n        if (!currentNode) {\r\n            throw new OwlError(\"No active component (a hook function should only be called in 'setup')\");\r\n        }\r\n        return currentNode;\r\n    }\r\n    function useComponent() {\r\n        return currentNode.component;\r\n    }\r\n    /**\r\n     * Apply default props (only top level).\r\n     */\r\n    function applyDefaultProps(props, defaultProps) {\r\n        for (let propName in defaultProps) {\r\n            if (props[propName] === undefined) {\r\n                props[propName] = defaultProps[propName];\r\n            }\r\n        }\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Integration with reactivity system (useState)\r\n    // -----------------------------------------------------------------------------\r\n    const batchedRenderFunctions = new WeakMap();\r\n    /**\r\n     * Creates a reactive object that will be observed by the current component.\r\n     * Reading data from the returned object (eg during rendering) will cause the\r\n     * component to subscribe to that data and be rerendered when it changes.\r\n     *\r\n     * @param state the state to observe\r\n     * @returns a reactive object that will cause the component to re-render on\r\n     *  relevant changes\r\n     * @see reactive\r\n     */\r\n    function useState(state) {\r\n        const node = getCurrent();\r\n        let render = batchedRenderFunctions.get(node);\r\n        if (!render) {\r\n            render = batched(node.render.bind(node, false));\r\n            batchedRenderFunctions.set(node, render);\r\n            // manual implementation of onWillDestroy to break cyclic dependency\r\n            node.willDestroy.push(clearReactivesForCallback.bind(null, render));\r\n        }\r\n        return reactive(state, render);\r\n    }\r\n    class ComponentNode {\r\n        constructor(C, props, app, parent, parentKey) {\r\n            this.fiber = null;\r\n            this.bdom = null;\r\n            this.status = 0 /* NEW */;\r\n            this.forceNextRender = false;\r\n            this.nextProps = null;\r\n            this.children = Object.create(null);\r\n            this.refs = {};\r\n            this.willStart = [];\r\n            this.willUpdateProps = [];\r\n            this.willUnmount = [];\r\n            this.mounted = [];\r\n            this.willPatch = [];\r\n            this.patched = [];\r\n            this.willDestroy = [];\r\n            currentNode = this;\r\n            this.app = app;\r\n            this.parent = parent;\r\n            this.props = props;\r\n            this.parentKey = parentKey;\r\n            const defaultProps = C.defaultProps;\r\n            props = Object.assign({}, props);\r\n            if (defaultProps) {\r\n                applyDefaultProps(props, defaultProps);\r\n            }\r\n            const env = (parent && parent.childEnv) || app.env;\r\n            this.childEnv = env;\r\n            for (const key in props) {\r\n                const prop = props[key];\r\n                if (prop && typeof prop === \"object\" && targets.has(prop)) {\r\n                    props[key] = useState(prop);\r\n                }\r\n            }\r\n            this.component = new C(props, env, this);\r\n            const ctx = Object.assign(Object.create(this.component), { this: this.component });\r\n            this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);\r\n            this.component.setup();\r\n            currentNode = null;\r\n        }\r\n        mountComponent(target, options) {\r\n            const fiber = new MountFiber(this, target, options);\r\n            this.app.scheduler.addFiber(fiber);\r\n            this.initiateRender(fiber);\r\n        }\r\n        async initiateRender(fiber) {\r\n            this.fiber = fiber;\r\n            if (this.mounted.length) {\r\n                fiber.root.mounted.push(fiber);\r\n            }\r\n            const component = this.component;\r\n            try {\r\n                await Promise.all(this.willStart.map((f) => f.call(component)));\r\n            }\r\n            catch (e) {\r\n                this.app.handleError({ node: this, error: e });\r\n                return;\r\n            }\r\n            if (this.status === 0 /* NEW */ && this.fiber === fiber) {\r\n                fiber.render();\r\n            }\r\n        }\r\n        async render(deep) {\r\n            if (this.status >= 2 /* CANCELLED */) {\r\n                return;\r\n            }\r\n            let current = this.fiber;\r\n            if (current && (current.root.locked || current.bdom === true)) {\r\n                await Promise.resolve();\r\n                // situation may have changed after the microtask tick\r\n                current = this.fiber;\r\n            }\r\n            if (current) {\r\n                if (!current.bdom && !fibersInError.has(current)) {\r\n                    if (deep) {\r\n                        // we want the render from this point on to be with deep=true\r\n                        current.deep = deep;\r\n                    }\r\n                    return;\r\n                }\r\n                // if current rendering was with deep=true, we want this one to be the same\r\n                deep = deep || current.deep;\r\n            }\r\n            else if (!this.bdom) {\r\n                return;\r\n            }\r\n            const fiber = makeRootFiber(this);\r\n            fiber.deep = deep;\r\n            this.fiber = fiber;\r\n            this.app.scheduler.addFiber(fiber);\r\n            await Promise.resolve();\r\n            if (this.status >= 2 /* CANCELLED */) {\r\n                return;\r\n            }\r\n            // We only want to actually render the component if the following two\r\n            // conditions are true:\r\n            // * this.fiber: it could be null, in which case the render has been cancelled\r\n            // * (current || !fiber.parent): if current is not null, this means that the\r\n            //   render function was called when a render was already occurring. In this\r\n            //   case, the pending rendering was cancelled, and the fiber needs to be\r\n            //   rendered to complete the work.  If current is null, we check that the\r\n            //   fiber has no parent.  If that is the case, the fiber was downgraded from\r\n            //   a root fiber to a child fiber in the previous microtick, because it was\r\n            //   embedded in a rendering coming from above, so the fiber will be rendered\r\n            //   in the next microtick anyway, so we should not render it again.\r\n            if (this.fiber === fiber && (current || !fiber.parent)) {\r\n                fiber.render();\r\n            }\r\n        }\r\n        cancel() {\r\n            this._cancel();\r\n            delete this.parent.children[this.parentKey];\r\n            this.app.scheduler.scheduleDestroy(this);\r\n        }\r\n        _cancel() {\r\n            this.status = 2 /* CANCELLED */;\r\n            const children = this.children;\r\n            for (let childKey in children) {\r\n                children[childKey]._cancel();\r\n            }\r\n        }\r\n        destroy() {\r\n            let shouldRemove = this.status === 1 /* MOUNTED */;\r\n            this._destroy();\r\n            if (shouldRemove) {\r\n                this.bdom.remove();\r\n            }\r\n        }\r\n        _destroy() {\r\n            const component = this.component;\r\n            if (this.status === 1 /* MOUNTED */) {\r\n                for (let cb of this.willUnmount) {\r\n                    cb.call(component);\r\n                }\r\n            }\r\n            for (let child of Object.values(this.children)) {\r\n                child._destroy();\r\n            }\r\n            if (this.willDestroy.length) {\r\n                try {\r\n                    for (let cb of this.willDestroy) {\r\n                        cb.call(component);\r\n                    }\r\n                }\r\n                catch (e) {\r\n                    this.app.handleError({ error: e, node: this });\r\n                }\r\n            }\r\n            this.status = 3 /* DESTROYED */;\r\n        }\r\n        async updateAndRender(props, parentFiber) {\r\n            this.nextProps = props;\r\n            props = Object.assign({}, props);\r\n            // update\r\n            const fiber = makeChildFiber(this, parentFiber);\r\n            this.fiber = fiber;\r\n            const component = this.component;\r\n            const defaultProps = component.constructor.defaultProps;\r\n            if (defaultProps) {\r\n                applyDefaultProps(props, defaultProps);\r\n            }\r\n            currentNode = this;\r\n            for (const key in props) {\r\n                const prop = props[key];\r\n                if (prop && typeof prop === \"object\" && targets.has(prop)) {\r\n                    props[key] = useState(prop);\r\n                }\r\n            }\r\n            currentNode = null;\r\n            const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));\r\n            await prom;\r\n            if (fiber !== this.fiber) {\r\n                return;\r\n            }\r\n            component.props = props;\r\n            fiber.render();\r\n            const parentRoot = parentFiber.root;\r\n            if (this.willPatch.length) {\r\n                parentRoot.willPatch.push(fiber);\r\n            }\r\n            if (this.patched.length) {\r\n                parentRoot.patched.push(fiber);\r\n            }\r\n        }\r\n        /**\r\n         * Finds a child that has dom that is not yet updated, and update it. This\r\n         * method is meant to be used only in the context of repatching the dom after\r\n         * a mounted hook failed and was handled.\r\n         */\r\n        updateDom() {\r\n            if (!this.fiber) {\r\n                return;\r\n            }\r\n            if (this.bdom === this.fiber.bdom) {\r\n                // If the error was handled by some child component, we need to find it to\r\n                // apply its change\r\n                for (let k in this.children) {\r\n                    const child = this.children[k];\r\n                    child.updateDom();\r\n                }\r\n            }\r\n            else {\r\n                // if we get here, this is the component that handled the error and rerendered\r\n                // itself, so we can simply patch the dom\r\n                this.bdom.patch(this.fiber.bdom, false);\r\n                this.fiber.appliedToDom = true;\r\n                this.fiber = null;\r\n            }\r\n        }\r\n        /**\r\n         * Sets a ref to a given HTMLElement.\r\n         *\r\n         * @param name the name of the ref to set\r\n         * @param el the HTMLElement to set the ref to. The ref is not set if the el\r\n         *  is null, but useRef will not return elements that are not in the DOM\r\n         */\r\n        setRef(name, el) {\r\n            if (el) {\r\n                this.refs[name] = el;\r\n            }\r\n        }\r\n        // ---------------------------------------------------------------------------\r\n        // Block DOM methods\r\n        // ---------------------------------------------------------------------------\r\n        firstNode() {\r\n            const bdom = this.bdom;\r\n            return bdom ? bdom.firstNode() : undefined;\r\n        }\r\n        mount(parent, anchor) {\r\n            const bdom = this.fiber.bdom;\r\n            this.bdom = bdom;\r\n            bdom.mount(parent, anchor);\r\n            this.status = 1 /* MOUNTED */;\r\n            this.fiber.appliedToDom = true;\r\n            this.children = this.fiber.childrenMap;\r\n            this.fiber = null;\r\n        }\r\n        moveBeforeDOMNode(node, parent) {\r\n            this.bdom.moveBeforeDOMNode(node, parent);\r\n        }\r\n        moveBeforeVNode(other, afterNode) {\r\n            this.bdom.moveBeforeVNode(other ? other.bdom : null, afterNode);\r\n        }\r\n        patch() {\r\n            if (this.fiber && this.fiber.parent) {\r\n                // we only patch here renderings coming from above. renderings initiated\r\n                // by the component will be patched independently in the appropriate\r\n                // fiber.complete\r\n                this._patch();\r\n                this.props = this.nextProps;\r\n            }\r\n        }\r\n        _patch() {\r\n            let hasChildren = false;\r\n            // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n            for (let _k in this.children) {\r\n                hasChildren = true;\r\n                break;\r\n            }\r\n            const fiber = this.fiber;\r\n            this.children = fiber.childrenMap;\r\n            this.bdom.patch(fiber.bdom, hasChildren);\r\n            fiber.appliedToDom = true;\r\n            this.fiber = null;\r\n        }\r\n        beforeRemove() {\r\n            this._destroy();\r\n        }\r\n        remove() {\r\n            this.bdom.remove();\r\n        }\r\n        // ---------------------------------------------------------------------------\r\n        // Some debug helpers\r\n        // ---------------------------------------------------------------------------\r\n        get name() {\r\n            return this.component.constructor.name;\r\n        }\r\n        get subscriptions() {\r\n            const render = batchedRenderFunctions.get(this);\r\n            return render ? getSubscriptions(render) : [];\r\n        }\r\n    }\r\n\r\n    const TIMEOUT = Symbol(\"timeout\");\r\n    const HOOK_TIMEOUT = {\r\n        onWillStart: 3000,\r\n        onWillUpdateProps: 3000,\r\n    };\r\n    function wrapError(fn, hookName) {\r\n        const error = new OwlError();\r\n        const timeoutError = new OwlError();\r\n        const node = getCurrent();\r\n        return (...args) => {\r\n            const onError = (cause) => {\r\n                error.cause = cause;\r\n                error.message =\r\n                    cause instanceof Error\r\n                        ? `The following error occurred in ${hookName}: \"${cause.message}\"`\r\n                        : `Something that is not an Error was thrown in ${hookName} (see this Error's \"cause\" property)`;\r\n                throw error;\r\n            };\r\n            let result;\r\n            try {\r\n                result = fn(...args);\r\n            }\r\n            catch (cause) {\r\n                onError(cause);\r\n            }\r\n            if (!(result instanceof Promise)) {\r\n                return result;\r\n            }\r\n            const timeout = HOOK_TIMEOUT[hookName];\r\n            if (timeout) {\r\n                const fiber = node.fiber;\r\n                Promise.race([\r\n                    result.catch(() => { }),\r\n                    new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), timeout)),\r\n                ]).then((res) => {\r\n                    if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {\r\n                        timeoutError.message = `${hookName}'s promise hasn't resolved after ${timeout / 1000} seconds`;\r\n                        console.log(timeoutError);\r\n                    }\r\n                });\r\n            }\r\n            return result.catch(onError);\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    //  hooks\r\n    // -----------------------------------------------------------------------------\r\n    function onWillStart(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.willStart.push(decorate(fn.bind(node.component), \"onWillStart\"));\r\n    }\r\n    function onWillUpdateProps(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.willUpdateProps.push(decorate(fn.bind(node.component), \"onWillUpdateProps\"));\r\n    }\r\n    function onMounted(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.mounted.push(decorate(fn.bind(node.component), \"onMounted\"));\r\n    }\r\n    function onWillPatch(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.willPatch.unshift(decorate(fn.bind(node.component), \"onWillPatch\"));\r\n    }\r\n    function onPatched(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.patched.push(decorate(fn.bind(node.component), \"onPatched\"));\r\n    }\r\n    function onWillUnmount(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.willUnmount.unshift(decorate(fn.bind(node.component), \"onWillUnmount\"));\r\n    }\r\n    function onWillDestroy(fn) {\r\n        const node = getCurrent();\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        node.willDestroy.push(decorate(fn.bind(node.component), \"onWillDestroy\"));\r\n    }\r\n    function onWillRender(fn) {\r\n        const node = getCurrent();\r\n        const renderFn = node.renderFn;\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        fn = decorate(fn.bind(node.component), \"onWillRender\");\r\n        node.renderFn = () => {\r\n            fn();\r\n            return renderFn();\r\n        };\r\n    }\r\n    function onRendered(fn) {\r\n        const node = getCurrent();\r\n        const renderFn = node.renderFn;\r\n        const decorate = node.app.dev ? wrapError : (fn) => fn;\r\n        fn = decorate(fn.bind(node.component), \"onRendered\");\r\n        node.renderFn = () => {\r\n            const result = renderFn();\r\n            fn();\r\n            return result;\r\n        };\r\n    }\r\n    function onError(callback) {\r\n        const node = getCurrent();\r\n        let handlers = nodeErrorHandlers.get(node);\r\n        if (!handlers) {\r\n            handlers = [];\r\n            nodeErrorHandlers.set(node, handlers);\r\n        }\r\n        handlers.push(callback.bind(node.component));\r\n    }\r\n\r\n    class Component {\r\n        constructor(props, env, node) {\r\n            this.props = props;\r\n            this.env = env;\r\n            this.__owl__ = node;\r\n        }\r\n        setup() { }\r\n        render(deep = false) {\r\n            this.__owl__.render(deep === true);\r\n        }\r\n    }\r\n    Component.template = \"\";\r\n\r\n    const VText = text(\"\").constructor;\r\n    class VPortal extends VText {\r\n        constructor(selector, content) {\r\n            super(\"\");\r\n            this.target = null;\r\n            this.selector = selector;\r\n            this.content = content;\r\n        }\r\n        mount(parent, anchor) {\r\n            super.mount(parent, anchor);\r\n            this.target = document.querySelector(this.selector);\r\n            if (this.target) {\r\n                this.content.mount(this.target, null);\r\n            }\r\n            else {\r\n                this.content.mount(parent, anchor);\r\n            }\r\n        }\r\n        beforeRemove() {\r\n            this.content.beforeRemove();\r\n        }\r\n        remove() {\r\n            if (this.content) {\r\n                super.remove();\r\n                this.content.remove();\r\n                this.content = null;\r\n            }\r\n        }\r\n        patch(other) {\r\n            super.patch(other);\r\n            if (this.content) {\r\n                this.content.patch(other.content, true);\r\n            }\r\n            else {\r\n                this.content = other.content;\r\n                this.content.mount(this.target, null);\r\n            }\r\n        }\r\n    }\r\n    /**\r\n     * kind of similar to <t t-slot=\"default\"/>, but it wraps it around a VPortal\r\n     */\r\n    function portalTemplate(app, bdom, helpers) {\r\n        let { callSlot } = helpers;\r\n        return function template(ctx, node, key = \"\") {\r\n            return new VPortal(ctx.props.target, callSlot(ctx, node, key, \"default\", false, null));\r\n        };\r\n    }\r\n    class Portal extends Component {\r\n        setup() {\r\n            const node = this.__owl__;\r\n            onMounted(() => {\r\n                const portal = node.bdom;\r\n                if (!portal.target) {\r\n                    const target = document.querySelector(this.props.target);\r\n                    if (target) {\r\n                        portal.content.moveBeforeDOMNode(target.firstChild, target);\r\n                    }\r\n                    else {\r\n                        throw new OwlError(\"invalid portal target\");\r\n                    }\r\n                }\r\n            });\r\n            onWillUnmount(() => {\r\n                const portal = node.bdom;\r\n                portal.remove();\r\n            });\r\n        }\r\n    }\r\n    Portal.template = \"__portal__\";\r\n    Portal.props = {\r\n        target: {\r\n            type: String,\r\n        },\r\n        slots: true,\r\n    };\r\n\r\n    // -----------------------------------------------------------------------------\r\n    // helpers\r\n    // -----------------------------------------------------------------------------\r\n    const isUnionType = (t) => Array.isArray(t);\r\n    const isBaseType = (t) => typeof t !== \"object\";\r\n    const isValueType = (t) => typeof t === \"object\" && t && \"value\" in t;\r\n    function isOptional(t) {\r\n        return typeof t === \"object\" && \"optional\" in t ? t.optional || false : false;\r\n    }\r\n    function describeType(type) {\r\n        return type === \"*\" || type === true ? \"value\" : type.name.toLowerCase();\r\n    }\r\n    function describe(info) {\r\n        if (isBaseType(info)) {\r\n            return describeType(info);\r\n        }\r\n        else if (isUnionType(info)) {\r\n            return info.map(describe).join(\" or \");\r\n        }\r\n        else if (isValueType(info)) {\r\n            return String(info.value);\r\n        }\r\n        if (\"element\" in info) {\r\n            return `list of ${describe({ type: info.element, optional: false })}s`;\r\n        }\r\n        if (\"shape\" in info) {\r\n            return `object`;\r\n        }\r\n        return describe(info.type || \"*\");\r\n    }\r\n    function toSchema(spec) {\r\n        return Object.fromEntries(spec.map((e) => e.endsWith(\"?\") ? [e.slice(0, -1), { optional: true }] : [e, { type: \"*\", optional: false }]));\r\n    }\r\n    /**\r\n     * Main validate function\r\n     */\r\n    function validate(obj, spec) {\r\n        let errors = validateSchema(obj, spec);\r\n        if (errors.length) {\r\n            throw new OwlError(\"Invalid object: \" + errors.join(\", \"));\r\n        }\r\n    }\r\n    /**\r\n     * Helper validate function, to get the list of errors. useful if one want to\r\n     * manipulate the errors without parsing an error object\r\n     */\r\n    function validateSchema(obj, schema) {\r\n        if (Array.isArray(schema)) {\r\n            schema = toSchema(schema);\r\n        }\r\n        obj = toRaw(obj);\r\n        let errors = [];\r\n        // check if each value in obj has correct shape\r\n        for (let key in obj) {\r\n            if (key in schema) {\r\n                let result = validateType(key, obj[key], schema[key]);\r\n                if (result) {\r\n                    errors.push(result);\r\n                }\r\n            }\r\n            else if (!(\"*\" in schema)) {\r\n                errors.push(`unknown key '${key}'`);\r\n            }\r\n        }\r\n        // check that all specified keys are defined in obj\r\n        for (let key in schema) {\r\n            const spec = schema[key];\r\n            if (key !== \"*\" && !isOptional(spec) && !(key in obj)) {\r\n                const isObj = typeof spec === \"object\" && !Array.isArray(spec);\r\n                const isAny = spec === \"*\" || (isObj && \"type\" in spec ? spec.type === \"*\" : isObj);\r\n                let detail = isAny ? \"\" : ` (should be a ${describe(spec)})`;\r\n                errors.push(`'${key}' is missing${detail}`);\r\n            }\r\n        }\r\n        return errors;\r\n    }\r\n    function validateBaseType(key, value, type) {\r\n        if (typeof type === \"function\") {\r\n            if (typeof value === \"object\") {\r\n                if (!(value instanceof type)) {\r\n                    return `'${key}' is not a ${describeType(type)}`;\r\n                }\r\n            }\r\n            else if (typeof value !== type.name.toLowerCase()) {\r\n                return `'${key}' is not a ${describeType(type)}`;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n    function validateArrayType(key, value, descr) {\r\n        if (!Array.isArray(value)) {\r\n            return `'${key}' is not a list of ${describe(descr)}s`;\r\n        }\r\n        for (let i = 0; i < value.length; i++) {\r\n            const error = validateType(`${key}[${i}]`, value[i], descr);\r\n            if (error) {\r\n                return error;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n    function validateType(key, value, descr) {\r\n        if (value === undefined) {\r\n            return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`;\r\n        }\r\n        else if (isBaseType(descr)) {\r\n            return validateBaseType(key, value, descr);\r\n        }\r\n        else if (isValueType(descr)) {\r\n            return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;\r\n        }\r\n        else if (isUnionType(descr)) {\r\n            let validDescr = descr.find((p) => !validateType(key, value, p));\r\n            return validDescr ? null : `'${key}' is not a ${describe(descr)}`;\r\n        }\r\n        let result = null;\r\n        if (\"element\" in descr) {\r\n            result = validateArrayType(key, value, descr.element);\r\n        }\r\n        else if (\"shape\" in descr) {\r\n            if (typeof value !== \"object\" || Array.isArray(value)) {\r\n                result = `'${key}' is not an object`;\r\n            }\r\n            else {\r\n                const errors = validateSchema(value, descr.shape);\r\n                if (errors.length) {\r\n                    result = `'${key}' doesn't have the correct shape (${errors.join(\", \")})`;\r\n                }\r\n            }\r\n        }\r\n        else if (\"values\" in descr) {\r\n            if (typeof value !== \"object\" || Array.isArray(value)) {\r\n                result = `'${key}' is not an object`;\r\n            }\r\n            else {\r\n                const errors = Object.entries(value)\r\n                    .map(([key, value]) => validateType(key, value, descr.values))\r\n                    .filter(Boolean);\r\n                if (errors.length) {\r\n                    result = `some of the values in '${key}' are invalid (${errors.join(\", \")})`;\r\n                }\r\n            }\r\n        }\r\n        if (\"type\" in descr && !result) {\r\n            result = validateType(key, value, descr.type);\r\n        }\r\n        if (\"validate\" in descr && !result) {\r\n            result = !descr.validate(value) ? `'${key}' is not valid` : null;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    const ObjectCreate = Object.create;\r\n    /**\r\n     * This file contains utility functions that will be injected in each template,\r\n     * to perform various useful tasks in the compiled code.\r\n     */\r\n    function withDefault(value, defaultValue) {\r\n        return value === undefined || value === null || value === false ? defaultValue : value;\r\n    }\r\n    function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {\r\n        key = key + \"__slot_\" + name;\r\n        const slots = ctx.props.slots || {};\r\n        const { __render, __ctx, __scope } = slots[name] || {};\r\n        const slotScope = ObjectCreate(__ctx || {});\r\n        if (__scope) {\r\n            slotScope[__scope] = extra;\r\n        }\r\n        const slotBDom = __render ? __render(slotScope, parent, key) : null;\r\n        if (defaultContent) {\r\n            let child1 = undefined;\r\n            let child2 = undefined;\r\n            if (slotBDom) {\r\n                child1 = dynamic ? toggler(name, slotBDom) : slotBDom;\r\n            }\r\n            else {\r\n                child2 = defaultContent(ctx, parent, key);\r\n            }\r\n            return multi([child1, child2]);\r\n        }\r\n        return slotBDom || text(\"\");\r\n    }\r\n    function capture(ctx) {\r\n        const result = ObjectCreate(ctx);\r\n        for (let k in ctx) {\r\n            result[k] = ctx[k];\r\n        }\r\n        return result;\r\n    }\r\n    function withKey(elem, k) {\r\n        elem.key = k;\r\n        return elem;\r\n    }\r\n    function prepareList(collection) {\r\n        let keys;\r\n        let values;\r\n        if (Array.isArray(collection)) {\r\n            keys = collection;\r\n            values = collection;\r\n        }\r\n        else if (collection instanceof Map) {\r\n            keys = [...collection.keys()];\r\n            values = [...collection.values()];\r\n        }\r\n        else if (Symbol.iterator in Object(collection)) {\r\n            keys = [...collection];\r\n            values = keys;\r\n        }\r\n        else if (collection && typeof collection === \"object\") {\r\n            values = Object.values(collection);\r\n            keys = Object.keys(collection);\r\n        }\r\n        else {\r\n            throw new OwlError(`Invalid loop expression: \"${collection}\" is not iterable`);\r\n        }\r\n        const n = values.length;\r\n        return [keys, values, n, new Array(n)];\r\n    }\r\n    const isBoundary = Symbol(\"isBoundary\");\r\n    function setContextValue(ctx, key, value) {\r\n        const ctx0 = ctx;\r\n        while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {\r\n            const newCtx = ctx.__proto__;\r\n            if (!newCtx) {\r\n                ctx = ctx0;\r\n                break;\r\n            }\r\n            ctx = newCtx;\r\n        }\r\n        ctx[key] = value;\r\n    }\r\n    function toNumber(val) {\r\n        const n = parseFloat(val);\r\n        return isNaN(n) ? val : n;\r\n    }\r\n    function shallowEqual(l1, l2) {\r\n        for (let i = 0, l = l1.length; i < l; i++) {\r\n            if (l1[i] !== l2[i]) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n    class LazyValue {\r\n        constructor(fn, ctx, component, node, key) {\r\n            this.fn = fn;\r\n            this.ctx = capture(ctx);\r\n            this.component = component;\r\n            this.node = node;\r\n            this.key = key;\r\n        }\r\n        evaluate() {\r\n            return this.fn.call(this.component, this.ctx, this.node, this.key);\r\n        }\r\n        toString() {\r\n            return this.evaluate().toString();\r\n        }\r\n    }\r\n    /*\r\n     * Safely outputs `value` as a block depending on the nature of `value`\r\n     */\r\n    function safeOutput(value, defaultValue) {\r\n        if (value === undefined || value === null) {\r\n            return defaultValue ? toggler(\"default\", defaultValue) : toggler(\"undefined\", text(\"\"));\r\n        }\r\n        let safeKey;\r\n        let block;\r\n        switch (typeof value) {\r\n            case \"object\":\r\n                if (value instanceof Markup) {\r\n                    safeKey = `string_safe`;\r\n                    block = html(value);\r\n                }\r\n                else if (value instanceof LazyValue) {\r\n                    safeKey = `lazy_value`;\r\n                    block = value.evaluate();\r\n                }\r\n                else if (value instanceof String) {\r\n                    safeKey = \"string_unsafe\";\r\n                    block = text(value);\r\n                }\r\n                else {\r\n                    // Assuming it is a block\r\n                    safeKey = \"block_safe\";\r\n                    block = value;\r\n                }\r\n                break;\r\n            case \"string\":\r\n                safeKey = \"string_unsafe\";\r\n                block = text(value);\r\n                break;\r\n            default:\r\n                safeKey = \"string_unsafe\";\r\n                block = text(String(value));\r\n        }\r\n        return toggler(safeKey, block);\r\n    }\r\n    /**\r\n     * Validate the component props (or next props) against the (static) props\r\n     * description.  This is potentially an expensive operation: it may needs to\r\n     * visit recursively the props and all the children to check if they are valid.\r\n     * This is why it is only done in 'dev' mode.\r\n     */\r\n    function validateProps(name, props, comp) {\r\n        const ComponentClass = typeof name !== \"string\"\r\n            ? name\r\n            : comp.constructor.components[name];\r\n        if (!ComponentClass) {\r\n            // this is an error, wrong component. We silently return here instead so the\r\n            // error is triggered by the usual path ('component' function)\r\n            return;\r\n        }\r\n        const schema = ComponentClass.props;\r\n        if (!schema) {\r\n            if (comp.__owl__.app.warnIfNoStaticProps) {\r\n                console.warn(`Component '${ComponentClass.name}' does not have a static props description`);\r\n            }\r\n            return;\r\n        }\r\n        const defaultProps = ComponentClass.defaultProps;\r\n        if (defaultProps) {\r\n            let isMandatory = (name) => Array.isArray(schema)\r\n                ? schema.includes(name)\r\n                : name in schema && !(\"*\" in schema) && !isOptional(schema[name]);\r\n            for (let p in defaultProps) {\r\n                if (isMandatory(p)) {\r\n                    throw new OwlError(`A default value cannot be defined for a mandatory prop (name: '${p}', component: ${ComponentClass.name})`);\r\n                }\r\n            }\r\n        }\r\n        const errors = validateSchema(props, schema);\r\n        if (errors.length) {\r\n            throw new OwlError(`Invalid props for component '${ComponentClass.name}': ` + errors.join(\", \"));\r\n        }\r\n    }\r\n    function makeRefWrapper(node) {\r\n        let refNames = new Set();\r\n        return (name, fn) => {\r\n            if (refNames.has(name)) {\r\n                throw new OwlError(`Cannot set the same ref more than once in the same component, ref \"${name}\" was set multiple times in ${node.name}`);\r\n            }\r\n            refNames.add(name);\r\n            return fn;\r\n        };\r\n    }\r\n    const helpers = {\r\n        withDefault,\r\n        zero: Symbol(\"zero\"),\r\n        isBoundary,\r\n        callSlot,\r\n        capture,\r\n        withKey,\r\n        prepareList,\r\n        setContextValue,\r\n        shallowEqual,\r\n        toNumber,\r\n        validateProps,\r\n        LazyValue,\r\n        safeOutput,\r\n        createCatcher,\r\n        markRaw,\r\n        OwlError,\r\n        makeRefWrapper,\r\n    };\r\n\r\n    /**\r\n     * Parses an XML string into an XML document, throwing errors on parser errors\r\n     * instead of returning an XML document containing the parseerror.\r\n     *\r\n     * @param xml the string to parse\r\n     * @returns an XML document corresponding to the content of the string\r\n     */\r\n    function parseXML(xml) {\r\n        const parser = new DOMParser();\r\n        const doc = parser.parseFromString(xml, \"text/xml\");\r\n        if (doc.getElementsByTagName(\"parsererror\").length) {\r\n            let msg = \"Invalid XML in template.\";\r\n            const parsererrorText = doc.getElementsByTagName(\"parsererror\")[0].textContent;\r\n            if (parsererrorText) {\r\n                msg += \"\\nThe parser has produced the following error message:\\n\" + parsererrorText;\r\n                const re = /\\d+/g;\r\n                const firstMatch = re.exec(parsererrorText);\r\n                if (firstMatch) {\r\n                    const lineNumber = Number(firstMatch[0]);\r\n                    const line = xml.split(\"\\n\")[lineNumber - 1];\r\n                    const secondMatch = re.exec(parsererrorText);\r\n                    if (line && secondMatch) {\r\n                        const columnIndex = Number(secondMatch[0]) - 1;\r\n                        if (line[columnIndex]) {\r\n                            msg +=\r\n                                `\\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\\n` +\r\n                                    `${line}\\n${\"-\".repeat(columnIndex - 1)}^`;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            throw new OwlError(msg);\r\n        }\r\n        return doc;\r\n    }\r\n\r\n    const bdom = { text, createBlock, list, multi, html, toggler, comment };\r\n    class TemplateSet {\r\n        constructor(config = {}) {\r\n            this.rawTemplates = Object.create(globalTemplates);\r\n            this.templates = {};\r\n            this.Portal = Portal;\r\n            this.dev = config.dev || false;\r\n            this.translateFn = config.translateFn;\r\n            this.translatableAttributes = config.translatableAttributes;\r\n            if (config.templates) {\r\n                if (config.templates instanceof Document || typeof config.templates === \"string\") {\r\n                    this.addTemplates(config.templates);\r\n                }\r\n                else {\r\n                    for (const name in config.templates) {\r\n                        this.addTemplate(name, config.templates[name]);\r\n                    }\r\n                }\r\n            }\r\n            this.getRawTemplate = config.getTemplate;\r\n            this.customDirectives = config.customDirectives || {};\r\n            this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };\r\n            this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);\r\n        }\r\n        static registerTemplate(name, fn) {\r\n            globalTemplates[name] = fn;\r\n        }\r\n        addTemplate(name, template) {\r\n            if (name in this.rawTemplates) {\r\n                // this check can be expensive, just silently ignore double definitions outside dev mode\r\n                if (!this.dev) {\r\n                    return;\r\n                }\r\n                const rawTemplate = this.rawTemplates[name];\r\n                const currentAsString = typeof rawTemplate === \"string\"\r\n                    ? rawTemplate\r\n                    : rawTemplate instanceof Element\r\n                        ? rawTemplate.outerHTML\r\n                        : rawTemplate.toString();\r\n                const newAsString = typeof template === \"string\" ? template : template.outerHTML;\r\n                if (currentAsString === newAsString) {\r\n                    return;\r\n                }\r\n                throw new OwlError(`Template ${name} already defined with different content`);\r\n            }\r\n            this.rawTemplates[name] = template;\r\n        }\r\n        addTemplates(xml) {\r\n            if (!xml) {\r\n                // empty string\r\n                return;\r\n            }\r\n            xml = xml instanceof Document ? xml : parseXML(xml);\r\n            for (const template of xml.querySelectorAll(\"[t-name]\")) {\r\n                const name = template.getAttribute(\"t-name\");\r\n                this.addTemplate(name, template);\r\n            }\r\n        }\r\n        getTemplate(name) {\r\n            var _a;\r\n            if (!(name in this.templates)) {\r\n                const rawTemplate = ((_a = this.getRawTemplate) === null || _a === void 0 ? void 0 : _a.call(this, name)) || this.rawTemplates[name];\r\n                if (rawTemplate === undefined) {\r\n                    let extraInfo = \"\";\r\n                    try {\r\n                        const componentName = getCurrent().component.constructor.name;\r\n                        extraInfo = ` (for component \"${componentName}\")`;\r\n                    }\r\n                    catch { }\r\n                    throw new OwlError(`Missing template: \"${name}\"${extraInfo}`);\r\n                }\r\n                const isFn = typeof rawTemplate === \"function\" && !(rawTemplate instanceof Element);\r\n                const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);\r\n                // first add a function to lazily get the template, in case there is a\r\n                // recursive call to the template name\r\n                const templates = this.templates;\r\n                this.templates[name] = function (context, parent) {\r\n                    return templates[name].call(this, context, parent);\r\n                };\r\n                const template = templateFn(this, bdom, this.runtimeUtils);\r\n                this.templates[name] = template;\r\n            }\r\n            return this.templates[name];\r\n        }\r\n        _compileTemplate(name, template) {\r\n            throw new OwlError(`Unable to compile a template. Please use owl full build instead`);\r\n        }\r\n        callTemplate(owner, subTemplate, ctx, parent, key) {\r\n            const template = this.getTemplate(subTemplate);\r\n            return toggler(subTemplate, template.call(owner, ctx, parent, key + subTemplate));\r\n        }\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    //  xml tag helper\r\n    // -----------------------------------------------------------------------------\r\n    const globalTemplates = {};\r\n    function xml(...args) {\r\n        const name = `__template__${xml.nextId++}`;\r\n        const value = String.raw(...args);\r\n        globalTemplates[name] = value;\r\n        return name;\r\n    }\r\n    xml.nextId = 1;\r\n    TemplateSet.registerTemplate(\"__portal__\", portalTemplate);\r\n\r\n    /**\r\n     * Owl QWeb Expression Parser\r\n     *\r\n     * Owl needs in various contexts to be able to understand the structure of a\r\n     * string representing a javascript expression.  The usual goal is to be able\r\n     * to rewrite some variables.  For example, if a template has\r\n     *\r\n     *  ```xml\r\n     *  <t t-if=\"computeSomething({val: state.val})\">...</t>\r\n     * ```\r\n     *\r\n     * this needs to be translated in something like this:\r\n     *\r\n     * ```js\r\n     *   if (context[\"computeSomething\"]({val: context[\"state\"].val})) { ... }\r\n     * ```\r\n     *\r\n     * This file contains the implementation of an extremely naive tokenizer/parser\r\n     * and evaluator for javascript expressions.  The supported grammar is basically\r\n     * only expressive enough to understand the shape of objects, of arrays, and\r\n     * various operators.\r\n     */\r\n    //------------------------------------------------------------------------------\r\n    // Misc types, constants and helpers\r\n    //------------------------------------------------------------------------------\r\n    const RESERVED_WORDS = \"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__\".split(\",\");\r\n    const WORD_REPLACEMENT = Object.assign(Object.create(null), {\r\n        and: \"&&\",\r\n        or: \"||\",\r\n        gt: \">\",\r\n        gte: \">=\",\r\n        lt: \"<\",\r\n        lte: \"<=\",\r\n    });\r\n    const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {\r\n        \"{\": \"LEFT_BRACE\",\r\n        \"}\": \"RIGHT_BRACE\",\r\n        \"[\": \"LEFT_BRACKET\",\r\n        \"]\": \"RIGHT_BRACKET\",\r\n        \":\": \"COLON\",\r\n        \",\": \"COMMA\",\r\n        \"(\": \"LEFT_PAREN\",\r\n        \")\": \"RIGHT_PAREN\",\r\n    });\r\n    // note that the space after typeof is relevant. It makes sure that the formatted\r\n    // expression has a space after typeof. Currently we don't support delete and void\r\n    const OPERATORS = \"...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~\".split(\",\");\r\n    let tokenizeString = function (expr) {\r\n        let s = expr[0];\r\n        let start = s;\r\n        if (s !== \"'\" && s !== '\"' && s !== \"`\") {\r\n            return false;\r\n        }\r\n        let i = 1;\r\n        let cur;\r\n        while (expr[i] && expr[i] !== start) {\r\n            cur = expr[i];\r\n            s += cur;\r\n            if (cur === \"\\\\\") {\r\n                i++;\r\n                cur = expr[i];\r\n                if (!cur) {\r\n                    throw new OwlError(\"Invalid expression\");\r\n                }\r\n                s += cur;\r\n            }\r\n            i++;\r\n        }\r\n        if (expr[i] !== start) {\r\n            throw new OwlError(\"Invalid expression\");\r\n        }\r\n        s += start;\r\n        if (start === \"`\") {\r\n            return {\r\n                type: \"TEMPLATE_STRING\",\r\n                value: s,\r\n                replace(replacer) {\r\n                    return s.replace(/\\$\\{(.*?)\\}/g, (match, group) => {\r\n                        return \"${\" + replacer(group) + \"}\";\r\n                    });\r\n                },\r\n            };\r\n        }\r\n        return { type: \"VALUE\", value: s };\r\n    };\r\n    let tokenizeNumber = function (expr) {\r\n        let s = expr[0];\r\n        if (s && s.match(/[0-9]/)) {\r\n            let i = 1;\r\n            while (expr[i] && expr[i].match(/[0-9]|\\./)) {\r\n                s += expr[i];\r\n                i++;\r\n            }\r\n            return { type: \"VALUE\", value: s };\r\n        }\r\n        else {\r\n            return false;\r\n        }\r\n    };\r\n    let tokenizeSymbol = function (expr) {\r\n        let s = expr[0];\r\n        if (s && s.match(/[a-zA-Z_\\$]/)) {\r\n            let i = 1;\r\n            while (expr[i] && expr[i].match(/\\w/)) {\r\n                s += expr[i];\r\n                i++;\r\n            }\r\n            if (s in WORD_REPLACEMENT) {\r\n                return { type: \"OPERATOR\", value: WORD_REPLACEMENT[s], size: s.length };\r\n            }\r\n            return { type: \"SYMBOL\", value: s };\r\n        }\r\n        else {\r\n            return false;\r\n        }\r\n    };\r\n    const tokenizeStatic = function (expr) {\r\n        const char = expr[0];\r\n        if (char && char in STATIC_TOKEN_MAP) {\r\n            return { type: STATIC_TOKEN_MAP[char], value: char };\r\n        }\r\n        return false;\r\n    };\r\n    const tokenizeOperator = function (expr) {\r\n        for (let op of OPERATORS) {\r\n            if (expr.startsWith(op)) {\r\n                return { type: \"OPERATOR\", value: op };\r\n            }\r\n        }\r\n        return false;\r\n    };\r\n    const TOKENIZERS = [\r\n        tokenizeString,\r\n        tokenizeNumber,\r\n        tokenizeOperator,\r\n        tokenizeSymbol,\r\n        tokenizeStatic,\r\n    ];\r\n    /**\r\n     * Convert a javascript expression (as a string) into a list of tokens. For\r\n     * example: `tokenize(\"1 + b\")` will return:\r\n     * ```js\r\n     *  [\r\n     *   {type: \"VALUE\", value: \"1\"},\r\n     *   {type: \"OPERATOR\", value: \"+\"},\r\n     *   {type: \"SYMBOL\", value: \"b\"}\r\n     * ]\r\n     * ```\r\n     */\r\n    function tokenize(expr) {\r\n        const result = [];\r\n        let token = true;\r\n        let error;\r\n        let current = expr;\r\n        try {\r\n            while (token) {\r\n                current = current.trim();\r\n                if (current) {\r\n                    for (let tokenizer of TOKENIZERS) {\r\n                        token = tokenizer(current);\r\n                        if (token) {\r\n                            result.push(token);\r\n                            current = current.slice(token.size || token.value.length);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    token = false;\r\n                }\r\n            }\r\n        }\r\n        catch (e) {\r\n            error = e; // Silence all errors and throw a generic error below\r\n        }\r\n        if (current.length || error) {\r\n            throw new OwlError(`Tokenizer error: could not tokenize \\`${expr}\\``);\r\n        }\r\n        return result;\r\n    }\r\n    //------------------------------------------------------------------------------\r\n    // Expression \"evaluator\"\r\n    //------------------------------------------------------------------------------\r\n    const isLeftSeparator = (token) => token && (token.type === \"LEFT_BRACE\" || token.type === \"COMMA\");\r\n    const isRightSeparator = (token) => token && (token.type === \"RIGHT_BRACE\" || token.type === \"COMMA\");\r\n    /**\r\n     * This is the main function exported by this file. This is the code that will\r\n     * process an expression (given as a string) and returns another expression with\r\n     * proper lookups in the context.\r\n     *\r\n     * Usually, this kind of code would be very simple to do if we had an AST (so,\r\n     * if we had a javascript parser), since then, we would only need to find the\r\n     * variables and replace them.  However, a parser is more complicated, and there\r\n     * are no standard builtin parser API.\r\n     *\r\n     * Since this method is applied to simple javasript expressions, and the work to\r\n     * be done is actually quite simple, we actually can get away with not using a\r\n     * parser, which helps with the code size.\r\n     *\r\n     * Here is the heuristic used by this method to determine if a token is a\r\n     * variable:\r\n     * - by default, all symbols are considered a variable\r\n     * - unless the previous token is a dot (in that case, this is a property: `a.b`)\r\n     * - or if the previous token is a left brace or a comma, and the next token is\r\n     *   a colon (in that case, this is an object key: `{a: b}`)\r\n     *\r\n     * Some specific code is also required to support arrow functions. If we detect\r\n     * the arrow operator, then we add the current (or some previous tokens) token to\r\n     * the list of variables so it does not get replaced by a lookup in the context\r\n     */\r\n    function compileExprToArray(expr) {\r\n        const localVars = new Set();\r\n        const tokens = tokenize(expr);\r\n        let i = 0;\r\n        let stack = []; // to track last opening (, [ or {\r\n        while (i < tokens.length) {\r\n            let token = tokens[i];\r\n            let prevToken = tokens[i - 1];\r\n            let nextToken = tokens[i + 1];\r\n            let groupType = stack[stack.length - 1];\r\n            switch (token.type) {\r\n                case \"LEFT_BRACE\":\r\n                case \"LEFT_BRACKET\":\r\n                case \"LEFT_PAREN\":\r\n                    stack.push(token.type);\r\n                    break;\r\n                case \"RIGHT_BRACE\":\r\n                case \"RIGHT_BRACKET\":\r\n                case \"RIGHT_PAREN\":\r\n                    stack.pop();\r\n            }\r\n            let isVar = token.type === \"SYMBOL\" && !RESERVED_WORDS.includes(token.value);\r\n            if (token.type === \"SYMBOL\" && !RESERVED_WORDS.includes(token.value)) {\r\n                if (prevToken) {\r\n                    // normalize missing tokens: {a} should be equivalent to {a:a}\r\n                    if (groupType === \"LEFT_BRACE\" &&\r\n                        isLeftSeparator(prevToken) &&\r\n                        isRightSeparator(nextToken)) {\r\n                        tokens.splice(i + 1, 0, { type: \"COLON\", value: \":\" }, { ...token });\r\n                        nextToken = tokens[i + 1];\r\n                    }\r\n                    if (prevToken.type === \"OPERATOR\" && prevToken.value === \".\") {\r\n                        isVar = false;\r\n                    }\r\n                    else if (prevToken.type === \"LEFT_BRACE\" || prevToken.type === \"COMMA\") {\r\n                        if (nextToken && nextToken.type === \"COLON\") {\r\n                            isVar = false;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (token.type === \"TEMPLATE_STRING\") {\r\n                token.value = token.replace((expr) => compileExpr(expr));\r\n            }\r\n            if (nextToken && nextToken.type === \"OPERATOR\" && nextToken.value === \"=>\") {\r\n                if (token.type === \"RIGHT_PAREN\") {\r\n                    let j = i - 1;\r\n                    while (j > 0 && tokens[j].type !== \"LEFT_PAREN\") {\r\n                        if (tokens[j].type === \"SYMBOL\" && tokens[j].originalValue) {\r\n                            tokens[j].value = tokens[j].originalValue;\r\n                            localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };\r\n                        }\r\n                        j--;\r\n                    }\r\n                }\r\n                else {\r\n                    localVars.add(token.value); //] = { id: token.value, expr: token.value };\r\n                }\r\n            }\r\n            if (isVar) {\r\n                token.varName = token.value;\r\n                if (!localVars.has(token.value)) {\r\n                    token.originalValue = token.value;\r\n                    token.value = `ctx['${token.value}']`;\r\n                }\r\n            }\r\n            i++;\r\n        }\r\n        // Mark all variables that have been used locally.\r\n        // This assumes the expression has only one scope (incorrect but \"good enough for now\")\r\n        for (const token of tokens) {\r\n            if (token.type === \"SYMBOL\" && token.varName && localVars.has(token.value)) {\r\n                token.originalValue = token.value;\r\n                token.value = `_${token.value}`;\r\n                token.isLocal = true;\r\n            }\r\n        }\r\n        return tokens;\r\n    }\r\n    // Leading spaces are trimmed during tokenization, so they need to be added back for some values\r\n    const paddedValues = new Map([[\"in \", \" in \"]]);\r\n    function compileExpr(expr) {\r\n        return compileExprToArray(expr)\r\n            .map((t) => paddedValues.get(t.value) || t.value)\r\n            .join(\"\");\r\n    }\r\n    const INTERP_REGEXP = /\\{\\{.*?\\}\\}|\\#\\{.*?\\}/g;\r\n    function replaceDynamicParts(s, replacer) {\r\n        let matches = s.match(INTERP_REGEXP);\r\n        if (matches && matches[0].length === s.length) {\r\n            return `(${replacer(s.slice(2, matches[0][0] === \"{\" ? -2 : -1))})`;\r\n        }\r\n        let r = s.replace(INTERP_REGEXP, (s) => \"${\" + replacer(s.slice(2, s[0] === \"{\" ? -2 : -1)) + \"}\");\r\n        return \"`\" + r + \"`\";\r\n    }\r\n    function interpolate(s) {\r\n        return replaceDynamicParts(s, compileExpr);\r\n    }\r\n\r\n    const whitespaceRE = /\\s+/g;\r\n    // using a non-html document so that <inner/outer>HTML serializes as XML instead\r\n    // of HTML (as we will parse it as xml later)\r\n    const xmlDoc = document.implementation.createDocument(null, null, null);\r\n    const MODS = new Set([\"stop\", \"capture\", \"prevent\", \"self\", \"synthetic\"]);\r\n    let nextDataIds = {};\r\n    function generateId(prefix = \"\") {\r\n        nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;\r\n        return prefix + nextDataIds[prefix];\r\n    }\r\n    function isProp(tag, key) {\r\n        switch (tag) {\r\n            case \"input\":\r\n                return (key === \"checked\" ||\r\n                    key === \"indeterminate\" ||\r\n                    key === \"value\" ||\r\n                    key === \"readonly\" ||\r\n                    key === \"readOnly\" ||\r\n                    key === \"disabled\");\r\n            case \"option\":\r\n                return key === \"selected\" || key === \"disabled\";\r\n            case \"textarea\":\r\n                return key === \"value\" || key === \"readonly\" || key === \"readOnly\" || key === \"disabled\";\r\n            case \"select\":\r\n                return key === \"value\" || key === \"disabled\";\r\n            case \"button\":\r\n            case \"optgroup\":\r\n                return key === \"disabled\";\r\n        }\r\n        return false;\r\n    }\r\n    /**\r\n     * Returns a template literal that evaluates to str. You can add interpolation\r\n     * sigils into the string if required\r\n     */\r\n    function toStringExpression(str) {\r\n        return `\\`${str.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/, \"\\\\${\")}\\``;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // BlockDescription\r\n    // -----------------------------------------------------------------------------\r\n    class BlockDescription {\r\n        constructor(target, type) {\r\n            this.dynamicTagName = null;\r\n            this.isRoot = false;\r\n            this.hasDynamicChildren = false;\r\n            this.children = [];\r\n            this.data = [];\r\n            this.childNumber = 0;\r\n            this.parentVar = \"\";\r\n            this.id = BlockDescription.nextBlockId++;\r\n            this.varName = \"b\" + this.id;\r\n            this.blockName = \"block\" + this.id;\r\n            this.target = target;\r\n            this.type = type;\r\n        }\r\n        insertData(str, prefix = \"d\") {\r\n            const id = generateId(prefix);\r\n            this.target.addLine(`let ${id} = ${str};`);\r\n            return this.data.push(id) - 1;\r\n        }\r\n        insert(dom) {\r\n            if (this.currentDom) {\r\n                this.currentDom.appendChild(dom);\r\n            }\r\n            else {\r\n                this.dom = dom;\r\n            }\r\n        }\r\n        generateExpr(expr) {\r\n            if (this.type === \"block\") {\r\n                const hasChildren = this.children.length;\r\n                let params = this.data.length ? `[${this.data.join(\", \")}]` : hasChildren ? \"[]\" : \"\";\r\n                if (hasChildren) {\r\n                    params += \", [\" + this.children.map((c) => c.varName).join(\", \") + \"]\";\r\n                }\r\n                if (this.dynamicTagName) {\r\n                    return `toggler(${this.dynamicTagName}, ${this.blockName}(${this.dynamicTagName})(${params}))`;\r\n                }\r\n                return `${this.blockName}(${params})`;\r\n            }\r\n            else if (this.type === \"list\") {\r\n                return `list(c_block${this.id})`;\r\n            }\r\n            return expr;\r\n        }\r\n        asXmlString() {\r\n            // Can't use outerHTML on text/comment nodes\r\n            // append dom to any element and use innerHTML instead\r\n            const t = xmlDoc.createElement(\"t\");\r\n            t.appendChild(this.dom);\r\n            return t.innerHTML;\r\n        }\r\n    }\r\n    BlockDescription.nextBlockId = 1;\r\n    function createContext(parentCtx, params) {\r\n        return Object.assign({\r\n            block: null,\r\n            index: 0,\r\n            forceNewBlock: true,\r\n            translate: parentCtx.translate,\r\n            translationCtx: parentCtx.translationCtx,\r\n            tKeyExpr: null,\r\n            nameSpace: parentCtx.nameSpace,\r\n            tModelSelectedExpr: parentCtx.tModelSelectedExpr,\r\n        }, params);\r\n    }\r\n    class CodeTarget {\r\n        constructor(name, on) {\r\n            this.indentLevel = 0;\r\n            this.loopLevel = 0;\r\n            this.code = [];\r\n            this.hasRoot = false;\r\n            this.hasCache = false;\r\n            this.shouldProtectScope = false;\r\n            this.hasRefWrapper = false;\r\n            this.name = name;\r\n            this.on = on || null;\r\n        }\r\n        addLine(line, idx) {\r\n            const prefix = new Array(this.indentLevel + 2).join(\"  \");\r\n            if (idx === undefined) {\r\n                this.code.push(prefix + line);\r\n            }\r\n            else {\r\n                this.code.splice(idx, 0, prefix + line);\r\n            }\r\n        }\r\n        generateCode() {\r\n            let result = [];\r\n            result.push(`function ${this.name}(ctx, node, key = \"\") {`);\r\n            if (this.shouldProtectScope) {\r\n                result.push(`  ctx = Object.create(ctx);`);\r\n                result.push(`  ctx[isBoundary] = 1`);\r\n            }\r\n            if (this.hasRefWrapper) {\r\n                result.push(`  let refWrapper = makeRefWrapper(this.__owl__);`);\r\n            }\r\n            if (this.hasCache) {\r\n                result.push(`  let cache = ctx.cache || {};`);\r\n                result.push(`  let nextCache = ctx.cache = {};`);\r\n            }\r\n            for (let line of this.code) {\r\n                result.push(line);\r\n            }\r\n            if (!this.hasRoot) {\r\n                result.push(`return text('');`);\r\n            }\r\n            result.push(`}`);\r\n            return result.join(\"\\n  \");\r\n        }\r\n        currentKey(ctx) {\r\n            let key = this.loopLevel ? `key${this.loopLevel}` : \"key\";\r\n            if (ctx.tKeyExpr) {\r\n                key = `${ctx.tKeyExpr} + ${key}`;\r\n            }\r\n            return key;\r\n        }\r\n    }\r\n    const TRANSLATABLE_ATTRS = [\r\n        \"alt\",\r\n        \"aria-label\",\r\n        \"aria-placeholder\",\r\n        \"aria-roledescription\",\r\n        \"aria-valuetext\",\r\n        \"label\",\r\n        \"placeholder\",\r\n        \"title\",\r\n    ];\r\n    const translationRE = /^(\\s*)([\\s\\S]+?)(\\s*)$/;\r\n    class CodeGenerator {\r\n        constructor(ast, options) {\r\n            this.blocks = [];\r\n            this.nextBlockId = 1;\r\n            this.isDebug = false;\r\n            this.targets = [];\r\n            this.target = new CodeTarget(\"template\");\r\n            this.translatableAttributes = TRANSLATABLE_ATTRS;\r\n            this.staticDefs = [];\r\n            this.slotNames = new Set();\r\n            this.helpers = new Set();\r\n            this.translateFn = options.translateFn || ((s) => s);\r\n            if (options.translatableAttributes) {\r\n                const attrs = new Set(TRANSLATABLE_ATTRS);\r\n                for (let attr of options.translatableAttributes) {\r\n                    if (attr.startsWith(\"-\")) {\r\n                        attrs.delete(attr.slice(1));\r\n                    }\r\n                    else {\r\n                        attrs.add(attr);\r\n                    }\r\n                }\r\n                this.translatableAttributes = [...attrs];\r\n            }\r\n            this.hasSafeContext = options.hasSafeContext || false;\r\n            this.dev = options.dev || false;\r\n            this.ast = ast;\r\n            this.templateName = options.name;\r\n            if (options.hasGlobalValues) {\r\n                this.helpers.add(\"__globals__\");\r\n            }\r\n        }\r\n        generateCode() {\r\n            const ast = this.ast;\r\n            this.isDebug = ast.type === 12 /* TDebug */;\r\n            BlockDescription.nextBlockId = 1;\r\n            nextDataIds = {};\r\n            this.compileAST(ast, {\r\n                block: null,\r\n                index: 0,\r\n                forceNewBlock: false,\r\n                isLast: true,\r\n                translate: true,\r\n                translationCtx: \"\",\r\n                tKeyExpr: null,\r\n            });\r\n            // define blocks and utility functions\r\n            let mainCode = [`  let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];\r\n            if (this.helpers.size) {\r\n                mainCode.push(`let { ${[...this.helpers].join(\", \")} } = helpers;`);\r\n            }\r\n            if (this.templateName) {\r\n                mainCode.push(`// Template name: \"${this.templateName}\"`);\r\n            }\r\n            for (let { id, expr } of this.staticDefs) {\r\n                mainCode.push(`const ${id} = ${expr};`);\r\n            }\r\n            // define all blocks\r\n            if (this.blocks.length) {\r\n                mainCode.push(``);\r\n                for (let block of this.blocks) {\r\n                    if (block.dom) {\r\n                        let xmlString = toStringExpression(block.asXmlString());\r\n                        if (block.dynamicTagName) {\r\n                            xmlString = xmlString.replace(/^`<\\w+/, `\\`<\\${tag || '${block.dom.nodeName}'}`);\r\n                            xmlString = xmlString.replace(/\\w+>`$/, `\\${tag || '${block.dom.nodeName}'}>\\``);\r\n                            mainCode.push(`let ${block.blockName} = tag => createBlock(${xmlString});`);\r\n                        }\r\n                        else {\r\n                            mainCode.push(`let ${block.blockName} = createBlock(${xmlString});`);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            // define all slots/defaultcontent function\r\n            if (this.targets.length) {\r\n                for (let fn of this.targets) {\r\n                    mainCode.push(\"\");\r\n                    mainCode = mainCode.concat(fn.generateCode());\r\n                }\r\n            }\r\n            // generate main code\r\n            mainCode.push(\"\");\r\n            mainCode = mainCode.concat(\"return \" + this.target.generateCode());\r\n            const code = mainCode.join(\"\\n  \");\r\n            if (this.isDebug) {\r\n                const msg = `[Owl Debug]\\n${code}`;\r\n                console.log(msg);\r\n            }\r\n            return code;\r\n        }\r\n        compileInNewTarget(prefix, ast, ctx, on) {\r\n            const name = generateId(prefix);\r\n            const initialTarget = this.target;\r\n            const target = new CodeTarget(name, on);\r\n            this.targets.push(target);\r\n            this.target = target;\r\n            this.compileAST(ast, createContext(ctx));\r\n            this.target = initialTarget;\r\n            return name;\r\n        }\r\n        addLine(line, idx) {\r\n            this.target.addLine(line, idx);\r\n        }\r\n        define(varName, expr) {\r\n            this.addLine(`const ${varName} = ${expr};`);\r\n        }\r\n        insertAnchor(block, index = block.children.length) {\r\n            const tag = `block-child-${index}`;\r\n            const anchor = xmlDoc.createElement(tag);\r\n            block.insert(anchor);\r\n        }\r\n        createBlock(parentBlock, type, ctx) {\r\n            const hasRoot = this.target.hasRoot;\r\n            const block = new BlockDescription(this.target, type);\r\n            if (!hasRoot) {\r\n                this.target.hasRoot = true;\r\n                block.isRoot = true;\r\n            }\r\n            if (parentBlock) {\r\n                parentBlock.children.push(block);\r\n                if (parentBlock.type === \"list\") {\r\n                    block.parentVar = `c_block${parentBlock.id}`;\r\n                }\r\n            }\r\n            return block;\r\n        }\r\n        insertBlock(expression, block, ctx) {\r\n            let blockExpr = block.generateExpr(expression);\r\n            if (block.parentVar) {\r\n                let key = this.target.currentKey(ctx);\r\n                this.helpers.add(\"withKey\");\r\n                this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);\r\n                return;\r\n            }\r\n            if (ctx.tKeyExpr) {\r\n                blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;\r\n            }\r\n            if (block.isRoot) {\r\n                if (this.target.on) {\r\n                    blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);\r\n                }\r\n                this.addLine(`return ${blockExpr};`);\r\n            }\r\n            else {\r\n                this.define(block.varName, blockExpr);\r\n            }\r\n        }\r\n        /**\r\n         * Captures variables that are used inside of an expression. This is useful\r\n         * because in compiled code, almost all variables are accessed through the ctx\r\n         * object. In the case of functions, that lookup in the context can be delayed\r\n         * which can cause issues if the value has changed since the function was\r\n         * defined.\r\n         *\r\n         * @param expr the expression to capture\r\n         * @param forceCapture whether the expression should capture its scope even if\r\n         *  it doesn't contain a function. Useful when the expression will be used as\r\n         *  a function body.\r\n         * @returns a new expression that uses the captured values\r\n         */\r\n        captureExpression(expr, forceCapture = false) {\r\n            if (!forceCapture && !expr.includes(\"=>\")) {\r\n                return compileExpr(expr);\r\n            }\r\n            const tokens = compileExprToArray(expr);\r\n            const mapping = new Map();\r\n            return tokens\r\n                .map((tok) => {\r\n                if (tok.varName && !tok.isLocal) {\r\n                    if (!mapping.has(tok.varName)) {\r\n                        const varId = generateId(\"v\");\r\n                        mapping.set(tok.varName, varId);\r\n                        this.define(varId, tok.value);\r\n                    }\r\n                    tok.value = mapping.get(tok.varName);\r\n                }\r\n                return tok.value;\r\n            })\r\n                .join(\"\");\r\n        }\r\n        translate(str, translationCtx) {\r\n            const match = translationRE.exec(str);\r\n            return match[1] + this.translateFn(match[2], translationCtx) + match[3];\r\n        }\r\n        /**\r\n         * @returns the newly created block name, if any\r\n         */\r\n        compileAST(ast, ctx) {\r\n            switch (ast.type) {\r\n                case 1 /* Comment */:\r\n                    return this.compileComment(ast, ctx);\r\n                case 0 /* Text */:\r\n                    return this.compileText(ast, ctx);\r\n                case 2 /* DomNode */:\r\n                    return this.compileTDomNode(ast, ctx);\r\n                case 4 /* TEsc */:\r\n                    return this.compileTEsc(ast, ctx);\r\n                case 8 /* TOut */:\r\n                    return this.compileTOut(ast, ctx);\r\n                case 5 /* TIf */:\r\n                    return this.compileTIf(ast, ctx);\r\n                case 9 /* TForEach */:\r\n                    return this.compileTForeach(ast, ctx);\r\n                case 10 /* TKey */:\r\n                    return this.compileTKey(ast, ctx);\r\n                case 3 /* Multi */:\r\n                    return this.compileMulti(ast, ctx);\r\n                case 7 /* TCall */:\r\n                    return this.compileTCall(ast, ctx);\r\n                case 15 /* TCallBlock */:\r\n                    return this.compileTCallBlock(ast, ctx);\r\n                case 6 /* TSet */:\r\n                    return this.compileTSet(ast, ctx);\r\n                case 11 /* TComponent */:\r\n                    return this.compileComponent(ast, ctx);\r\n                case 12 /* TDebug */:\r\n                    return this.compileDebug(ast, ctx);\r\n                case 13 /* TLog */:\r\n                    return this.compileLog(ast, ctx);\r\n                case 14 /* TSlot */:\r\n                    return this.compileTSlot(ast, ctx);\r\n                case 16 /* TTranslation */:\r\n                    return this.compileTTranslation(ast, ctx);\r\n                case 17 /* TTranslationContext */:\r\n                    return this.compileTTranslationContext(ast, ctx);\r\n                case 18 /* TPortal */:\r\n                    return this.compileTPortal(ast, ctx);\r\n            }\r\n        }\r\n        compileDebug(ast, ctx) {\r\n            this.addLine(`debugger;`);\r\n            if (ast.content) {\r\n                return this.compileAST(ast.content, ctx);\r\n            }\r\n            return null;\r\n        }\r\n        compileLog(ast, ctx) {\r\n            this.addLine(`console.log(${compileExpr(ast.expr)});`);\r\n            if (ast.content) {\r\n                return this.compileAST(ast.content, ctx);\r\n            }\r\n            return null;\r\n        }\r\n        compileComment(ast, ctx) {\r\n            let { block, forceNewBlock } = ctx;\r\n            const isNewBlock = !block || forceNewBlock;\r\n            if (isNewBlock) {\r\n                block = this.createBlock(block, \"comment\", ctx);\r\n                this.insertBlock(`comment(${toStringExpression(ast.value)})`, block, {\r\n                    ...ctx,\r\n                    forceNewBlock: forceNewBlock && !block,\r\n                });\r\n            }\r\n            else {\r\n                const text = xmlDoc.createComment(ast.value);\r\n                block.insert(text);\r\n            }\r\n            return block.varName;\r\n        }\r\n        compileText(ast, ctx) {\r\n            let { block, forceNewBlock } = ctx;\r\n            let value = ast.value;\r\n            if (value && ctx.translate !== false) {\r\n                value = this.translate(value, ctx.translationCtx);\r\n            }\r\n            if (!ctx.inPreTag) {\r\n                value = value.replace(whitespaceRE, \" \");\r\n            }\r\n            if (!block || forceNewBlock) {\r\n                block = this.createBlock(block, \"text\", ctx);\r\n                this.insertBlock(`text(${toStringExpression(value)})`, block, {\r\n                    ...ctx,\r\n                    forceNewBlock: forceNewBlock && !block,\r\n                });\r\n            }\r\n            else {\r\n                const createFn = ast.type === 0 /* Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;\r\n                block.insert(createFn.call(xmlDoc, value));\r\n            }\r\n            return block.varName;\r\n        }\r\n        generateHandlerCode(rawEvent, handler) {\r\n            const modifiers = rawEvent\r\n                .split(\".\")\r\n                .slice(1)\r\n                .map((m) => {\r\n                if (!MODS.has(m)) {\r\n                    throw new OwlError(`Unknown event modifier: '${m}'`);\r\n                }\r\n                return `\"${m}\"`;\r\n            });\r\n            let modifiersCode = \"\";\r\n            if (modifiers.length) {\r\n                modifiersCode = `${modifiers.join(\",\")}, `;\r\n            }\r\n            return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;\r\n        }\r\n        compileTDomNode(ast, ctx) {\r\n            var _a;\r\n            let { block, forceNewBlock } = ctx;\r\n            const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;\r\n            let codeIdx = this.target.code.length;\r\n            if (isNewBlock) {\r\n                if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {\r\n                    this.insertAnchor(ctx.block);\r\n                }\r\n                block = this.createBlock(block, \"block\", ctx);\r\n                this.blocks.push(block);\r\n                if (ast.dynamicTag) {\r\n                    const tagExpr = generateId(\"tag\");\r\n                    this.define(tagExpr, compileExpr(ast.dynamicTag));\r\n                    block.dynamicTagName = tagExpr;\r\n                }\r\n            }\r\n            // attributes\r\n            const attrs = {};\r\n            for (let key in ast.attrs) {\r\n                let expr, attrName;\r\n                if (key.startsWith(\"t-attf\")) {\r\n                    expr = interpolate(ast.attrs[key]);\r\n                    const idx = block.insertData(expr, \"attr\");\r\n                    attrName = key.slice(7);\r\n                    attrs[\"block-attribute-\" + idx] = attrName;\r\n                }\r\n                else if (key.startsWith(\"t-att\")) {\r\n                    attrName = key === \"t-att\" ? null : key.slice(6);\r\n                    expr = compileExpr(ast.attrs[key]);\r\n                    if (attrName && isProp(ast.tag, attrName)) {\r\n                        if (attrName === \"readonly\") {\r\n                            // the property has a different name than the attribute\r\n                            attrName = \"readOnly\";\r\n                        }\r\n                        // we force a new string or new boolean to bypass the equality check in blockdom when patching same value\r\n                        if (attrName === \"value\") {\r\n                            // When the expression is falsy (except 0), fall back to an empty string\r\n                            expr = `new String((${expr}) === 0 ? 0 : ((${expr}) || \"\"))`;\r\n                        }\r\n                        else {\r\n                            expr = `new Boolean(${expr})`;\r\n                        }\r\n                        const idx = block.insertData(expr, \"prop\");\r\n                        attrs[`block-property-${idx}`] = attrName;\r\n                    }\r\n                    else {\r\n                        const idx = block.insertData(expr, \"attr\");\r\n                        if (key === \"t-att\") {\r\n                            attrs[`block-attributes`] = String(idx);\r\n                        }\r\n                        else {\r\n                            attrs[`block-attribute-${idx}`] = attrName;\r\n                        }\r\n                    }\r\n                }\r\n                else if (this.translatableAttributes.includes(key)) {\r\n                    const attrTranslationCtx = ((_a = ast.attrsTranslationCtx) === null || _a === void 0 ? void 0 : _a[key]) || ctx.translationCtx;\r\n                    attrs[key] = this.translateFn(ast.attrs[key], attrTranslationCtx);\r\n                }\r\n                else {\r\n                    expr = `\"${ast.attrs[key]}\"`;\r\n                    attrName = key;\r\n                    attrs[key] = ast.attrs[key];\r\n                }\r\n                if (attrName === \"value\" && ctx.tModelSelectedExpr) {\r\n                    let selectedId = block.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, \"attr\");\r\n                    attrs[`block-attribute-${selectedId}`] = \"selected\";\r\n                }\r\n            }\r\n            // t-model\r\n            let tModelSelectedExpr;\r\n            if (ast.model) {\r\n                const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;\r\n                const baseExpression = compileExpr(baseExpr);\r\n                const bExprId = generateId(\"bExpr\");\r\n                this.define(bExprId, baseExpression);\r\n                const expression = compileExpr(expr);\r\n                const exprId = generateId(\"expr\");\r\n                this.define(exprId, expression);\r\n                const fullExpression = `${bExprId}[${exprId}]`;\r\n                let idx;\r\n                if (specialInitTargetAttr) {\r\n                    let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;\r\n                    if (!targetExpr && ast.attrs) {\r\n                        // look at the dynamic attribute counterpart\r\n                        const dynamicTgExpr = ast.attrs[`t-att-${targetAttr}`];\r\n                        if (dynamicTgExpr) {\r\n                            targetExpr = compileExpr(dynamicTgExpr);\r\n                        }\r\n                    }\r\n                    idx = block.insertData(`${fullExpression} === ${targetExpr}`, \"prop\");\r\n                    attrs[`block-property-${idx}`] = specialInitTargetAttr;\r\n                }\r\n                else if (hasDynamicChildren) {\r\n                    const bValueId = generateId(\"bValue\");\r\n                    tModelSelectedExpr = `${bValueId}`;\r\n                    this.define(tModelSelectedExpr, fullExpression);\r\n                }\r\n                else {\r\n                    idx = block.insertData(`${fullExpression}`, \"prop\");\r\n                    attrs[`block-property-${idx}`] = targetAttr;\r\n                }\r\n                this.helpers.add(\"toNumber\");\r\n                let valueCode = `ev.target.${targetAttr}`;\r\n                valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;\r\n                valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;\r\n                const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;\r\n                idx = block.insertData(handler, \"hdlr\");\r\n                attrs[`block-handler-${idx}`] = eventType;\r\n            }\r\n            // event handlers\r\n            for (let ev in ast.on) {\r\n                const name = this.generateHandlerCode(ev, ast.on[ev]);\r\n                const idx = block.insertData(name, \"hdlr\");\r\n                attrs[`block-handler-${idx}`] = ev;\r\n            }\r\n            // t-ref\r\n            if (ast.ref) {\r\n                if (this.dev) {\r\n                    this.helpers.add(\"makeRefWrapper\");\r\n                    this.target.hasRefWrapper = true;\r\n                }\r\n                const isDynamic = INTERP_REGEXP.test(ast.ref);\r\n                let name = `\\`${ast.ref}\\``;\r\n                if (isDynamic) {\r\n                    name = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));\r\n                }\r\n                let setRefStr = `(el) => this.__owl__.setRef((${name}), el)`;\r\n                if (this.dev) {\r\n                    setRefStr = `refWrapper(${name}, ${setRefStr})`;\r\n                }\r\n                const idx = block.insertData(setRefStr, \"ref\");\r\n                attrs[\"block-ref\"] = String(idx);\r\n            }\r\n            const nameSpace = ast.ns || ctx.nameSpace;\r\n            const dom = nameSpace\r\n                ? xmlDoc.createElementNS(nameSpace, ast.tag)\r\n                : xmlDoc.createElement(ast.tag);\r\n            for (const [attr, val] of Object.entries(attrs)) {\r\n                if (!(attr === \"class\" && val === \"\")) {\r\n                    dom.setAttribute(attr, val);\r\n                }\r\n            }\r\n            block.insert(dom);\r\n            if (ast.content.length) {\r\n                const initialDom = block.currentDom;\r\n                block.currentDom = dom;\r\n                const children = ast.content;\r\n                for (let i = 0; i < children.length; i++) {\r\n                    const child = ast.content[i];\r\n                    const subCtx = createContext(ctx, {\r\n                        block,\r\n                        index: block.childNumber,\r\n                        forceNewBlock: false,\r\n                        isLast: ctx.isLast && i === children.length - 1,\r\n                        tKeyExpr: ctx.tKeyExpr,\r\n                        nameSpace,\r\n                        tModelSelectedExpr,\r\n                        inPreTag: ctx.inPreTag || ast.tag === \"pre\",\r\n                    });\r\n                    this.compileAST(child, subCtx);\r\n                }\r\n                block.currentDom = initialDom;\r\n            }\r\n            if (isNewBlock) {\r\n                this.insertBlock(`${block.blockName}(ddd)`, block, ctx);\r\n                // may need to rewrite code!\r\n                if (block.children.length && block.hasDynamicChildren) {\r\n                    const code = this.target.code;\r\n                    const children = block.children.slice();\r\n                    let current = children.shift();\r\n                    for (let i = codeIdx; i < code.length; i++) {\r\n                        if (code[i].trimStart().startsWith(`const ${current.varName} `)) {\r\n                            code[i] = code[i].replace(`const ${current.varName}`, current.varName);\r\n                            current = children.shift();\r\n                            if (!current)\r\n                                break;\r\n                        }\r\n                    }\r\n                    this.addLine(`let ${block.children.map((c) => c.varName).join(\", \")};`, codeIdx);\r\n                }\r\n            }\r\n            return block.varName;\r\n        }\r\n        compileTEsc(ast, ctx) {\r\n            let { block, forceNewBlock } = ctx;\r\n            let expr;\r\n            if (ast.expr === \"0\") {\r\n                this.helpers.add(\"zero\");\r\n                expr = `ctx[zero]`;\r\n            }\r\n            else {\r\n                expr = compileExpr(ast.expr);\r\n                if (ast.defaultValue) {\r\n                    this.helpers.add(\"withDefault\");\r\n                    // FIXME: defaultValue is not translated\r\n                    expr = `withDefault(${expr}, ${toStringExpression(ast.defaultValue)})`;\r\n                }\r\n            }\r\n            if (!block || forceNewBlock) {\r\n                block = this.createBlock(block, \"text\", ctx);\r\n                this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block });\r\n            }\r\n            else {\r\n                const idx = block.insertData(expr, \"txt\");\r\n                const text = xmlDoc.createElement(`block-text-${idx}`);\r\n                block.insert(text);\r\n            }\r\n            return block.varName;\r\n        }\r\n        compileTOut(ast, ctx) {\r\n            let { block } = ctx;\r\n            if (block) {\r\n                this.insertAnchor(block);\r\n            }\r\n            block = this.createBlock(block, \"html\", ctx);\r\n            let blockStr;\r\n            if (ast.expr === \"0\") {\r\n                this.helpers.add(\"zero\");\r\n                blockStr = `ctx[zero]`;\r\n            }\r\n            else if (ast.body) {\r\n                let bodyValue = null;\r\n                bodyValue = BlockDescription.nextBlockId;\r\n                const subCtx = createContext(ctx);\r\n                this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);\r\n                this.helpers.add(\"safeOutput\");\r\n                blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;\r\n            }\r\n            else {\r\n                this.helpers.add(\"safeOutput\");\r\n                blockStr = `safeOutput(${compileExpr(ast.expr)})`;\r\n            }\r\n            this.insertBlock(blockStr, block, ctx);\r\n            return block.varName;\r\n        }\r\n        compileTIfBranch(content, block, ctx) {\r\n            this.target.indentLevel++;\r\n            let childN = block.children.length;\r\n            this.compileAST(content, createContext(ctx, { block, index: ctx.index }));\r\n            if (block.children.length > childN) {\r\n                // we have some content => need to insert an anchor at correct index\r\n                this.insertAnchor(block, childN);\r\n            }\r\n            this.target.indentLevel--;\r\n        }\r\n        compileTIf(ast, ctx, nextNode) {\r\n            let { block, forceNewBlock } = ctx;\r\n            const codeIdx = this.target.code.length;\r\n            const isNewBlock = !block || (block.type !== \"multi\" && forceNewBlock);\r\n            if (block) {\r\n                block.hasDynamicChildren = true;\r\n            }\r\n            if (!block || (block.type !== \"multi\" && forceNewBlock)) {\r\n                block = this.createBlock(block, \"multi\", ctx);\r\n            }\r\n            this.addLine(`if (${compileExpr(ast.condition)}) {`);\r\n            this.compileTIfBranch(ast.content, block, ctx);\r\n            if (ast.tElif) {\r\n                for (let clause of ast.tElif) {\r\n                    this.addLine(`} else if (${compileExpr(clause.condition)}) {`);\r\n                    this.compileTIfBranch(clause.content, block, ctx);\r\n                }\r\n            }\r\n            if (ast.tElse) {\r\n                this.addLine(`} else {`);\r\n                this.compileTIfBranch(ast.tElse, block, ctx);\r\n            }\r\n            this.addLine(\"}\");\r\n            if (isNewBlock) {\r\n                // note: this part is duplicated from end of compiledomnode:\r\n                if (block.children.length) {\r\n                    const code = this.target.code;\r\n                    const children = block.children.slice();\r\n                    let current = children.shift();\r\n                    for (let i = codeIdx; i < code.length; i++) {\r\n                        if (code[i].trimStart().startsWith(`const ${current.varName} `)) {\r\n                            code[i] = code[i].replace(`const ${current.varName}`, current.varName);\r\n                            current = children.shift();\r\n                            if (!current)\r\n                                break;\r\n                        }\r\n                    }\r\n                    this.addLine(`let ${block.children.map((c) => c.varName).join(\", \")};`, codeIdx);\r\n                }\r\n                // note: this part is duplicated from end of compilemulti:\r\n                const args = block.children.map((c) => c.varName).join(\", \");\r\n                this.insertBlock(`multi([${args}])`, block, ctx);\r\n            }\r\n            return block.varName;\r\n        }\r\n        compileTForeach(ast, ctx) {\r\n            let { block } = ctx;\r\n            if (block) {\r\n                this.insertAnchor(block);\r\n            }\r\n            block = this.createBlock(block, \"list\", ctx);\r\n            this.target.loopLevel++;\r\n            const loopVar = `i${this.target.loopLevel}`;\r\n            this.addLine(`ctx = Object.create(ctx);`);\r\n            const vals = `v_block${block.id}`;\r\n            const keys = `k_block${block.id}`;\r\n            const l = `l_block${block.id}`;\r\n            const c = `c_block${block.id}`;\r\n            this.helpers.add(\"prepareList\");\r\n            this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);\r\n            // Throw errors on duplicate keys in dev mode\r\n            if (this.dev) {\r\n                this.define(`keys${block.id}`, `new Set()`);\r\n            }\r\n            this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);\r\n            this.target.indentLevel++;\r\n            this.addLine(`ctx[\\`${ast.elem}\\`] = ${keys}[${loopVar}];`);\r\n            if (!ast.hasNoFirst) {\r\n                this.addLine(`ctx[\\`${ast.elem}_first\\`] = ${loopVar} === 0;`);\r\n            }\r\n            if (!ast.hasNoLast) {\r\n                this.addLine(`ctx[\\`${ast.elem}_last\\`] = ${loopVar} === ${keys}.length - 1;`);\r\n            }\r\n            if (!ast.hasNoIndex) {\r\n                this.addLine(`ctx[\\`${ast.elem}_index\\`] = ${loopVar};`);\r\n            }\r\n            if (!ast.hasNoValue) {\r\n                this.addLine(`ctx[\\`${ast.elem}_value\\`] = ${vals}[${loopVar}];`);\r\n            }\r\n            this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);\r\n            if (this.dev) {\r\n                // Throw error on duplicate keys in dev mode\r\n                this.helpers.add(\"OwlError\");\r\n                this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\\`Got duplicate key in t-foreach: \\${key${this.target.loopLevel}}\\`)}`);\r\n                this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);\r\n            }\r\n            let id;\r\n            if (ast.memo) {\r\n                this.target.hasCache = true;\r\n                id = generateId();\r\n                this.define(`memo${id}`, compileExpr(ast.memo));\r\n                this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);\r\n                this.addLine(`if (vnode${id}) {`);\r\n                this.target.indentLevel++;\r\n                this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);\r\n                this.target.indentLevel++;\r\n                this.addLine(`${c}[${loopVar}] = vnode${id};`);\r\n                this.addLine(`nextCache[key${this.target.loopLevel}] = vnode${id};`);\r\n                this.addLine(`continue;`);\r\n                this.target.indentLevel--;\r\n                this.addLine(\"}\");\r\n                this.target.indentLevel--;\r\n                this.addLine(\"}\");\r\n            }\r\n            const subCtx = createContext(ctx, { block, index: loopVar });\r\n            this.compileAST(ast.body, subCtx);\r\n            if (ast.memo) {\r\n                this.addLine(`nextCache[key${this.target.loopLevel}] = Object.assign(${c}[${loopVar}], {memo: memo${id}});`);\r\n            }\r\n            this.target.indentLevel--;\r\n            this.target.loopLevel--;\r\n            this.addLine(`}`);\r\n            if (!ctx.isLast) {\r\n                this.addLine(`ctx = ctx.__proto__;`);\r\n            }\r\n            this.insertBlock(\"l\", block, ctx);\r\n            return block.varName;\r\n        }\r\n        compileTKey(ast, ctx) {\r\n            const tKeyExpr = generateId(\"tKey_\");\r\n            this.define(tKeyExpr, compileExpr(ast.expr));\r\n            ctx = createContext(ctx, {\r\n                tKeyExpr,\r\n                block: ctx.block,\r\n                index: ctx.index,\r\n            });\r\n            return this.compileAST(ast.content, ctx);\r\n        }\r\n        compileMulti(ast, ctx) {\r\n            let { block, forceNewBlock } = ctx;\r\n            const isNewBlock = !block || forceNewBlock;\r\n            let codeIdx = this.target.code.length;\r\n            if (isNewBlock) {\r\n                const n = ast.content.filter((c) => !c.hasNoRepresentation).length;\r\n                let result = null;\r\n                if (n <= 1) {\r\n                    for (let child of ast.content) {\r\n                        const blockName = this.compileAST(child, ctx);\r\n                        result = result || blockName;\r\n                    }\r\n                    return result;\r\n                }\r\n                block = this.createBlock(block, \"multi\", ctx);\r\n            }\r\n            let index = 0;\r\n            for (let i = 0, l = ast.content.length; i < l; i++) {\r\n                const child = ast.content[i];\r\n                const forceNewBlock = !child.hasNoRepresentation;\r\n                const subCtx = createContext(ctx, {\r\n                    block,\r\n                    index,\r\n                    forceNewBlock,\r\n                    isLast: ctx.isLast && i === l - 1,\r\n                });\r\n                this.compileAST(child, subCtx);\r\n                if (forceNewBlock) {\r\n                    index++;\r\n                }\r\n            }\r\n            if (isNewBlock) {\r\n                if (block.hasDynamicChildren && block.children.length) {\r\n                    const code = this.target.code;\r\n                    const children = block.children.slice();\r\n                    let current = children.shift();\r\n                    for (let i = codeIdx; i < code.length; i++) {\r\n                        if (code[i].trimStart().startsWith(`const ${current.varName} `)) {\r\n                            code[i] = code[i].replace(`const ${current.varName}`, current.varName);\r\n                            current = children.shift();\r\n                            if (!current)\r\n                                break;\r\n                        }\r\n                    }\r\n                    this.addLine(`let ${block.children.map((c) => c.varName).join(\", \")};`, codeIdx);\r\n                }\r\n                const args = block.children.map((c) => c.varName).join(\", \");\r\n                this.insertBlock(`multi([${args}])`, block, ctx);\r\n            }\r\n            return block.varName;\r\n        }\r\n        compileTCall(ast, ctx) {\r\n            let { block, forceNewBlock } = ctx;\r\n            let ctxVar = ctx.ctxVar || \"ctx\";\r\n            if (ast.context) {\r\n                ctxVar = generateId(\"ctx\");\r\n                this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);\r\n            }\r\n            const isDynamic = INTERP_REGEXP.test(ast.name);\r\n            const subTemplate = isDynamic ? interpolate(ast.name) : \"`\" + ast.name + \"`\";\r\n            if (block && !forceNewBlock) {\r\n                this.insertAnchor(block);\r\n            }\r\n            block = this.createBlock(block, \"multi\", ctx);\r\n            if (ast.body) {\r\n                this.addLine(`${ctxVar} = Object.create(${ctxVar});`);\r\n                this.addLine(`${ctxVar}[isBoundary] = 1;`);\r\n                this.helpers.add(\"isBoundary\");\r\n                const subCtx = createContext(ctx, { ctxVar });\r\n                const bl = this.compileMulti({ type: 3 /* Multi */, content: ast.body }, subCtx);\r\n                if (bl) {\r\n                    this.helpers.add(\"zero\");\r\n                    this.addLine(`${ctxVar}[zero] = ${bl};`);\r\n                }\r\n            }\r\n            const key = this.generateComponentKey();\r\n            if (isDynamic) {\r\n                const templateVar = generateId(\"template\");\r\n                if (!this.staticDefs.find((d) => d.id === \"call\")) {\r\n                    this.staticDefs.push({ id: \"call\", expr: `app.callTemplate.bind(app)` });\r\n                }\r\n                this.define(templateVar, subTemplate);\r\n                this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {\r\n                    ...ctx,\r\n                    forceNewBlock: !block,\r\n                });\r\n            }\r\n            else {\r\n                const id = generateId(`callTemplate_`);\r\n                this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });\r\n                this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {\r\n                    ...ctx,\r\n                    forceNewBlock: !block,\r\n                });\r\n            }\r\n            if (ast.body && !ctx.isLast) {\r\n                this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);\r\n            }\r\n            return block.varName;\r\n        }\r\n        compileTCallBlock(ast, ctx) {\r\n            let { block, forceNewBlock } = ctx;\r\n            if (block) {\r\n                if (!forceNewBlock) {\r\n                    this.insertAnchor(block);\r\n                }\r\n            }\r\n            block = this.createBlock(block, \"multi\", ctx);\r\n            this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });\r\n            return block.varName;\r\n        }\r\n        compileTSet(ast, ctx) {\r\n            this.target.shouldProtectScope = true;\r\n            this.helpers.add(\"isBoundary\").add(\"withDefault\");\r\n            const expr = ast.value ? compileExpr(ast.value || \"\") : \"null\";\r\n            if (ast.body) {\r\n                this.helpers.add(\"LazyValue\");\r\n                const bodyAst = { type: 3 /* Multi */, content: ast.body };\r\n                const name = this.compileInNewTarget(\"value\", bodyAst, ctx);\r\n                let key = this.target.currentKey(ctx);\r\n                let value = `new LazyValue(${name}, ctx, this, node, ${key})`;\r\n                value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;\r\n                this.addLine(`ctx[\\`${ast.name}\\`] = ${value};`);\r\n            }\r\n            else {\r\n                let value;\r\n                if (ast.defaultValue) {\r\n                    const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue);\r\n                    if (ast.value) {\r\n                        value = `withDefault(${expr}, ${defaultValue})`;\r\n                    }\r\n                    else {\r\n                        value = defaultValue;\r\n                    }\r\n                }\r\n                else {\r\n                    value = expr;\r\n                }\r\n                this.helpers.add(\"setContextValue\");\r\n                this.addLine(`setContextValue(${ctx.ctxVar || \"ctx\"}, \"${ast.name}\", ${value});`);\r\n            }\r\n            return null;\r\n        }\r\n        generateComponentKey(currentKey = \"key\") {\r\n            const parts = [generateId(\"__\")];\r\n            for (let i = 0; i < this.target.loopLevel; i++) {\r\n                parts.push(`\\${key${i + 1}}`);\r\n            }\r\n            return `${currentKey} + \\`${parts.join(\"__\")}\\``;\r\n        }\r\n        /**\r\n         * Formats a prop name and value into a string suitable to be inserted in the\r\n         * generated code. For example:\r\n         *\r\n         * Name              Value            Result\r\n         * ---------------------------------------------------------\r\n         * \"number\"          \"state\"          \"number: ctx['state']\"\r\n         * \"something\"       \"\"               \"something: undefined\"\r\n         * \"some-prop\"       \"state\"          \"'some-prop': ctx['state']\"\r\n         * \"onClick.bind\"    \"onClick\"        \"onClick: bind(ctx, ctx['onClick'])\"\r\n         */\r\n        formatProp(name, value, attrsTranslationCtx, translationCtx) {\r\n            if (name.endsWith(\".translate\")) {\r\n                const attrTranslationCtx = (attrsTranslationCtx === null || attrsTranslationCtx === void 0 ? void 0 : attrsTranslationCtx[name]) || translationCtx;\r\n                value = toStringExpression(this.translateFn(value, attrTranslationCtx));\r\n            }\r\n            else {\r\n                value = this.captureExpression(value);\r\n            }\r\n            if (name.includes(\".\")) {\r\n                let [_name, suffix] = name.split(\".\");\r\n                name = _name;\r\n                switch (suffix) {\r\n                    case \"bind\":\r\n                        value = `(${value}).bind(this)`;\r\n                        break;\r\n                    case \"alike\":\r\n                    case \"translate\":\r\n                        break;\r\n                    default:\r\n                        throw new OwlError(`Invalid prop suffix: ${suffix}`);\r\n                }\r\n            }\r\n            name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;\r\n            return `${name}: ${value || undefined}`;\r\n        }\r\n        formatPropObject(obj, attrsTranslationCtx, translationCtx) {\r\n            return Object.entries(obj).map(([k, v]) => this.formatProp(k, v, attrsTranslationCtx, translationCtx));\r\n        }\r\n        getPropString(props, dynProps) {\r\n            let propString = `{${props.join(\",\")}}`;\r\n            if (dynProps) {\r\n                propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? \", \" + propString : \"\"})`;\r\n            }\r\n            return propString;\r\n        }\r\n        compileComponent(ast, ctx) {\r\n            let { block } = ctx;\r\n            // props\r\n            const hasSlotsProp = \"slots\" in (ast.props || {});\r\n            const props = ast.props\r\n                ? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)\r\n                : [];\r\n            // slots\r\n            let slotDef = \"\";\r\n            if (ast.slots) {\r\n                let ctxStr = \"ctx\";\r\n                if (this.target.loopLevel || !this.hasSafeContext) {\r\n                    ctxStr = generateId(\"ctx\");\r\n                    this.helpers.add(\"capture\");\r\n                    this.define(ctxStr, `capture(ctx)`);\r\n                }\r\n                let slotStr = [];\r\n                for (let slotName in ast.slots) {\r\n                    const slotAst = ast.slots[slotName];\r\n                    const params = [];\r\n                    if (slotAst.content) {\r\n                        const name = this.compileInNewTarget(\"slot\", slotAst.content, ctx, slotAst.on);\r\n                        params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);\r\n                    }\r\n                    const scope = ast.slots[slotName].scope;\r\n                    if (scope) {\r\n                        params.push(`__scope: \"${scope}\"`);\r\n                    }\r\n                    if (ast.slots[slotName].attrs) {\r\n                        params.push(...this.formatPropObject(ast.slots[slotName].attrs, ast.slots[slotName].attrsTranslationCtx, ctx.translationCtx));\r\n                    }\r\n                    const slotInfo = `{${params.join(\", \")}}`;\r\n                    slotStr.push(`'${slotName}': ${slotInfo}`);\r\n                }\r\n                slotDef = `{${slotStr.join(\", \")}}`;\r\n            }\r\n            if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {\r\n                this.helpers.add(\"markRaw\");\r\n                props.push(`slots: markRaw(${slotDef})`);\r\n            }\r\n            let propString = this.getPropString(props, ast.dynamicProps);\r\n            let propVar;\r\n            if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {\r\n                propVar = generateId(\"props\");\r\n                this.define(propVar, propString);\r\n                propString = propVar;\r\n            }\r\n            if (slotDef && (ast.dynamicProps || hasSlotsProp)) {\r\n                this.helpers.add(\"markRaw\");\r\n                this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);\r\n            }\r\n            // cmap key\r\n            let expr;\r\n            if (ast.isDynamic) {\r\n                expr = generateId(\"Comp\");\r\n                this.define(expr, compileExpr(ast.name));\r\n            }\r\n            else {\r\n                expr = `\\`${ast.name}\\``;\r\n            }\r\n            if (this.dev) {\r\n                this.addLine(`helpers.validateProps(${expr}, ${propVar}, this);`);\r\n            }\r\n            if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {\r\n                // todo: check the forcenewblock condition\r\n                this.insertAnchor(block);\r\n            }\r\n            let keyArg = this.generateComponentKey();\r\n            if (ctx.tKeyExpr) {\r\n                keyArg = `${ctx.tKeyExpr} + ${keyArg}`;\r\n            }\r\n            let id = generateId(\"comp\");\r\n            const propList = [];\r\n            for (let p in ast.props || {}) {\r\n                let [name, suffix] = p.split(\".\");\r\n                if (!suffix) {\r\n                    propList.push(`\"${name}\"`);\r\n                }\r\n            }\r\n            this.staticDefs.push({\r\n                id,\r\n                expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, [${propList}])`,\r\n            });\r\n            if (ast.isDynamic) {\r\n                // If the component class changes, this can cause delayed renders to go\r\n                // through if the key doesn't change. Use the component name for now.\r\n                // This means that two component classes with the same name isn't supported\r\n                // in t-component. We can generate a unique id per class later if needed.\r\n                keyArg = `(${expr}).name + ${keyArg}`;\r\n            }\r\n            let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;\r\n            if (ast.isDynamic) {\r\n                blockExpr = `toggler(${expr}, ${blockExpr})`;\r\n            }\r\n            // event handling\r\n            if (ast.on) {\r\n                blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);\r\n            }\r\n            block = this.createBlock(block, \"multi\", ctx);\r\n            this.insertBlock(blockExpr, block, ctx);\r\n            return block.varName;\r\n        }\r\n        wrapWithEventCatcher(expr, on) {\r\n            this.helpers.add(\"createCatcher\");\r\n            let name = generateId(\"catcher\");\r\n            let spec = {};\r\n            let handlers = [];\r\n            for (let ev in on) {\r\n                let handlerId = generateId(\"hdlr\");\r\n                let idx = handlers.push(handlerId) - 1;\r\n                spec[ev] = idx;\r\n                const handler = this.generateHandlerCode(ev, on[ev]);\r\n                this.define(handlerId, handler);\r\n            }\r\n            this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });\r\n            return `${name}(${expr}, [${handlers.join(\",\")}])`;\r\n        }\r\n        compileTSlot(ast, ctx) {\r\n            this.helpers.add(\"callSlot\");\r\n            let { block } = ctx;\r\n            let blockString;\r\n            let slotName;\r\n            let dynamic = false;\r\n            let isMultiple = false;\r\n            if (ast.name.match(INTERP_REGEXP)) {\r\n                dynamic = true;\r\n                isMultiple = true;\r\n                slotName = interpolate(ast.name);\r\n            }\r\n            else {\r\n                slotName = \"'\" + ast.name + \"'\";\r\n                isMultiple = isMultiple || this.slotNames.has(ast.name);\r\n                this.slotNames.add(ast.name);\r\n            }\r\n            const attrs = { ...ast.attrs };\r\n            const dynProps = attrs[\"t-props\"];\r\n            delete attrs[\"t-props\"];\r\n            let key = this.target.loopLevel ? `key${this.target.loopLevel}` : \"key\";\r\n            if (isMultiple) {\r\n                key = this.generateComponentKey(key);\r\n            }\r\n            const props = ast.attrs\r\n                ? this.formatPropObject(attrs, ast.attrsTranslationCtx, ctx.translationCtx)\r\n                : [];\r\n            const scope = this.getPropString(props, dynProps);\r\n            if (ast.defaultContent) {\r\n                const name = this.compileInNewTarget(\"defaultContent\", ast.defaultContent, ctx);\r\n                blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name}.bind(this))`;\r\n            }\r\n            else {\r\n                if (dynamic) {\r\n                    let name = generateId(\"slot\");\r\n                    this.define(name, slotName);\r\n                    blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;\r\n                }\r\n                else {\r\n                    blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;\r\n                }\r\n            }\r\n            // event handling\r\n            if (ast.on) {\r\n                blockString = this.wrapWithEventCatcher(blockString, ast.on);\r\n            }\r\n            if (block) {\r\n                this.insertAnchor(block);\r\n            }\r\n            block = this.createBlock(block, \"multi\", ctx);\r\n            this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });\r\n            return block.varName;\r\n        }\r\n        compileTTranslation(ast, ctx) {\r\n            if (ast.content) {\r\n                return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));\r\n            }\r\n            return null;\r\n        }\r\n        compileTTranslationContext(ast, ctx) {\r\n            if (ast.content) {\r\n                return this.compileAST(ast.content, Object.assign({}, ctx, { translationCtx: ast.translationCtx }));\r\n            }\r\n            return null;\r\n        }\r\n        compileTPortal(ast, ctx) {\r\n            if (!this.staticDefs.find((d) => d.id === \"Portal\")) {\r\n                this.staticDefs.push({ id: \"Portal\", expr: `app.Portal` });\r\n            }\r\n            let { block } = ctx;\r\n            const name = this.compileInNewTarget(\"slot\", ast.content, ctx);\r\n            let ctxStr = \"ctx\";\r\n            if (this.target.loopLevel || !this.hasSafeContext) {\r\n                ctxStr = generateId(\"ctx\");\r\n                this.helpers.add(\"capture\");\r\n                this.define(ctxStr, `capture(ctx)`);\r\n            }\r\n            let id = generateId(\"comp\");\r\n            this.staticDefs.push({\r\n                id,\r\n                expr: `app.createComponent(null, false, true, false, false)`,\r\n            });\r\n            const target = compileExpr(ast.target);\r\n            const key = this.generateComponentKey();\r\n            const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, ${key}, node, ctx, Portal)`;\r\n            if (block) {\r\n                this.insertAnchor(block);\r\n            }\r\n            block = this.createBlock(block, \"multi\", ctx);\r\n            this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });\r\n            return block.varName;\r\n        }\r\n    }\r\n\r\n    // -----------------------------------------------------------------------------\r\n    // Parser\r\n    // -----------------------------------------------------------------------------\r\n    const cache = new WeakMap();\r\n    function parse(xml, customDir) {\r\n        const ctx = {\r\n            inPreTag: false,\r\n            customDirectives: customDir,\r\n        };\r\n        if (typeof xml === \"string\") {\r\n            const elem = parseXML(`<t>${xml}</t>`).firstChild;\r\n            return _parse(elem, ctx);\r\n        }\r\n        let ast = cache.get(xml);\r\n        if (!ast) {\r\n            // we clone here the xml to prevent modifying it in place\r\n            ast = _parse(xml.cloneNode(true), ctx);\r\n            cache.set(xml, ast);\r\n        }\r\n        return ast;\r\n    }\r\n    function _parse(xml, ctx) {\r\n        normalizeXML(xml);\r\n        return parseNode(xml, ctx) || { type: 0 /* Text */, value: \"\" };\r\n    }\r\n    function parseNode(node, ctx) {\r\n        if (!(node instanceof Element)) {\r\n            return parseTextCommentNode(node, ctx);\r\n        }\r\n        return (parseTCustom(node, ctx) ||\r\n            parseTDebugLog(node, ctx) ||\r\n            parseTForEach(node, ctx) ||\r\n            parseTIf(node, ctx) ||\r\n            parseTPortal(node, ctx) ||\r\n            parseTCall(node, ctx) ||\r\n            parseTCallBlock(node) ||\r\n            parseTTranslation(node, ctx) ||\r\n            parseTTranslationContext(node, ctx) ||\r\n            parseTKey(node, ctx) ||\r\n            parseTEscNode(node, ctx) ||\r\n            parseTOutNode(node, ctx) ||\r\n            parseTSlot(node, ctx) ||\r\n            parseComponent(node, ctx) ||\r\n            parseDOMNode(node, ctx) ||\r\n            parseTSetNode(node, ctx) ||\r\n            parseTNode(node, ctx));\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // <t /> tag\r\n    // -----------------------------------------------------------------------------\r\n    function parseTNode(node, ctx) {\r\n        if (node.tagName !== \"t\") {\r\n            return null;\r\n        }\r\n        return parseChildNodes(node, ctx);\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Text and Comment Nodes\r\n    // -----------------------------------------------------------------------------\r\n    const lineBreakRE = /[\\r\\n]/;\r\n    function parseTextCommentNode(node, ctx) {\r\n        if (node.nodeType === Node.TEXT_NODE) {\r\n            let value = node.textContent || \"\";\r\n            if (!ctx.inPreTag && lineBreakRE.test(value) && !value.trim()) {\r\n                return null;\r\n            }\r\n            return { type: 0 /* Text */, value };\r\n        }\r\n        else if (node.nodeType === Node.COMMENT_NODE) {\r\n            return { type: 1 /* Comment */, value: node.textContent || \"\" };\r\n        }\r\n        return null;\r\n    }\r\n    function parseTCustom(node, ctx) {\r\n        if (!ctx.customDirectives) {\r\n            return null;\r\n        }\r\n        const nodeAttrsNames = node.getAttributeNames();\r\n        for (let attr of nodeAttrsNames) {\r\n            if (attr === \"t-custom\" || attr === \"t-custom-\") {\r\n                throw new OwlError(\"Missing custom directive name with t-custom directive\");\r\n            }\r\n            if (attr.startsWith(\"t-custom-\")) {\r\n                const directiveName = attr.split(\".\")[0].slice(9);\r\n                const customDirective = ctx.customDirectives[directiveName];\r\n                if (!customDirective) {\r\n                    throw new OwlError(`Custom directive \"${directiveName}\" is not defined`);\r\n                }\r\n                const value = node.getAttribute(attr);\r\n                const modifiers = attr.split(\".\").slice(1);\r\n                node.removeAttribute(attr);\r\n                try {\r\n                    customDirective(node, value, modifiers);\r\n                }\r\n                catch (error) {\r\n                    throw new OwlError(`Custom directive \"${directiveName}\" throw the following error: ${error}`);\r\n                }\r\n                return parseNode(node, ctx);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // debugging\r\n    // -----------------------------------------------------------------------------\r\n    function parseTDebugLog(node, ctx) {\r\n        if (node.hasAttribute(\"t-debug\")) {\r\n            node.removeAttribute(\"t-debug\");\r\n            const content = parseNode(node, ctx);\r\n            const ast = {\r\n                type: 12 /* TDebug */,\r\n                content,\r\n            };\r\n            if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {\r\n                ast.hasNoRepresentation = true;\r\n            }\r\n            return ast;\r\n        }\r\n        if (node.hasAttribute(\"t-log\")) {\r\n            const expr = node.getAttribute(\"t-log\");\r\n            node.removeAttribute(\"t-log\");\r\n            const content = parseNode(node, ctx);\r\n            const ast = {\r\n                type: 13 /* TLog */,\r\n                expr,\r\n                content,\r\n            };\r\n            if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {\r\n                ast.hasNoRepresentation = true;\r\n            }\r\n            return ast;\r\n        }\r\n        return null;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Regular dom node\r\n    // -----------------------------------------------------------------------------\r\n    const hasDotAtTheEnd = /\\.[\\w_]+\\s*$/;\r\n    const hasBracketsAtTheEnd = /\\[[^\\[]+\\]\\s*$/;\r\n    const ROOT_SVG_TAGS = new Set([\"svg\", \"g\", \"path\"]);\r\n    function parseDOMNode(node, ctx) {\r\n        const { tagName } = node;\r\n        const dynamicTag = node.getAttribute(\"t-tag\");\r\n        node.removeAttribute(\"t-tag\");\r\n        if (tagName === \"t\" && !dynamicTag) {\r\n            return null;\r\n        }\r\n        if (tagName.startsWith(\"block-\")) {\r\n            throw new OwlError(`Invalid tag name: '${tagName}'`);\r\n        }\r\n        ctx = Object.assign({}, ctx);\r\n        if (tagName === \"pre\") {\r\n            ctx.inPreTag = true;\r\n        }\r\n        let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? \"http://www.w3.org/2000/svg\" : null;\r\n        const ref = node.getAttribute(\"t-ref\");\r\n        node.removeAttribute(\"t-ref\");\r\n        const nodeAttrsNames = node.getAttributeNames();\r\n        let attrs = null;\r\n        let attrsTranslationCtx = null;\r\n        let on = null;\r\n        let model = null;\r\n        for (let attr of nodeAttrsNames) {\r\n            const value = node.getAttribute(attr);\r\n            if (attr === \"t-on\" || attr === \"t-on-\") {\r\n                throw new OwlError(\"Missing event name with t-on directive\");\r\n            }\r\n            if (attr.startsWith(\"t-on-\")) {\r\n                on = on || {};\r\n                on[attr.slice(5)] = value;\r\n            }\r\n            else if (attr.startsWith(\"t-model\")) {\r\n                if (![\"input\", \"select\", \"textarea\"].includes(tagName)) {\r\n                    throw new OwlError(\"The t-model directive only works with <input>, <textarea> and <select>\");\r\n                }\r\n                let baseExpr, expr;\r\n                if (hasDotAtTheEnd.test(value)) {\r\n                    const index = value.lastIndexOf(\".\");\r\n                    baseExpr = value.slice(0, index);\r\n                    expr = `'${value.slice(index + 1)}'`;\r\n                }\r\n                else if (hasBracketsAtTheEnd.test(value)) {\r\n                    const index = value.lastIndexOf(\"[\");\r\n                    baseExpr = value.slice(0, index);\r\n                    expr = value.slice(index + 1, -1);\r\n                }\r\n                else {\r\n                    throw new OwlError(`Invalid t-model expression: \"${value}\" (it should be assignable)`);\r\n                }\r\n                const typeAttr = node.getAttribute(\"type\");\r\n                const isInput = tagName === \"input\";\r\n                const isSelect = tagName === \"select\";\r\n                const isCheckboxInput = isInput && typeAttr === \"checkbox\";\r\n                const isRadioInput = isInput && typeAttr === \"radio\";\r\n                const hasTrimMod = attr.includes(\".trim\");\r\n                const hasLazyMod = hasTrimMod || attr.includes(\".lazy\");\r\n                const hasNumberMod = attr.includes(\".number\");\r\n                const eventType = isRadioInput ? \"click\" : isSelect || hasLazyMod ? \"change\" : \"input\";\r\n                model = {\r\n                    baseExpr,\r\n                    expr,\r\n                    targetAttr: isCheckboxInput ? \"checked\" : \"value\",\r\n                    specialInitTargetAttr: isRadioInput ? \"checked\" : null,\r\n                    eventType,\r\n                    hasDynamicChildren: false,\r\n                    shouldTrim: hasTrimMod,\r\n                    shouldNumberize: hasNumberMod,\r\n                };\r\n                if (isSelect) {\r\n                    // don't pollute the original ctx\r\n                    ctx = Object.assign({}, ctx);\r\n                    ctx.tModelInfo = model;\r\n                }\r\n            }\r\n            else if (attr.startsWith(\"block-\")) {\r\n                throw new OwlError(`Invalid attribute: '${attr}'`);\r\n            }\r\n            else if (attr === \"xmlns\") {\r\n                ns = value;\r\n            }\r\n            else if (attr.startsWith(\"t-translation-context-\")) {\r\n                const attrName = attr.slice(22);\r\n                attrsTranslationCtx = attrsTranslationCtx || {};\r\n                attrsTranslationCtx[attrName] = value;\r\n            }\r\n            else if (attr !== \"t-name\") {\r\n                if (attr.startsWith(\"t-\") && !attr.startsWith(\"t-att\")) {\r\n                    throw new OwlError(`Unknown QWeb directive: '${attr}'`);\r\n                }\r\n                const tModel = ctx.tModelInfo;\r\n                if (tModel && [\"t-att-value\", \"t-attf-value\"].includes(attr)) {\r\n                    tModel.hasDynamicChildren = true;\r\n                }\r\n                attrs = attrs || {};\r\n                attrs[attr] = value;\r\n            }\r\n        }\r\n        if (ns) {\r\n            ctx.nameSpace = ns;\r\n        }\r\n        const children = parseChildren(node, ctx);\r\n        return {\r\n            type: 2 /* DomNode */,\r\n            tag: tagName,\r\n            dynamicTag,\r\n            attrs,\r\n            attrsTranslationCtx,\r\n            on,\r\n            ref,\r\n            content: children,\r\n            model,\r\n            ns,\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-esc\r\n    // -----------------------------------------------------------------------------\r\n    function parseTEscNode(node, ctx) {\r\n        if (!node.hasAttribute(\"t-esc\")) {\r\n            return null;\r\n        }\r\n        const escValue = node.getAttribute(\"t-esc\");\r\n        node.removeAttribute(\"t-esc\");\r\n        const tesc = {\r\n            type: 4 /* TEsc */,\r\n            expr: escValue,\r\n            defaultValue: node.textContent || \"\",\r\n        };\r\n        let ref = node.getAttribute(\"t-ref\");\r\n        node.removeAttribute(\"t-ref\");\r\n        const ast = parseNode(node, ctx);\r\n        if (!ast) {\r\n            return tesc;\r\n        }\r\n        if (ast.type === 2 /* DomNode */) {\r\n            return {\r\n                ...ast,\r\n                ref,\r\n                content: [tesc],\r\n            };\r\n        }\r\n        return tesc;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-out\r\n    // -----------------------------------------------------------------------------\r\n    function parseTOutNode(node, ctx) {\r\n        if (!node.hasAttribute(\"t-out\") && !node.hasAttribute(\"t-raw\")) {\r\n            return null;\r\n        }\r\n        if (node.hasAttribute(\"t-raw\")) {\r\n            console.warn(`t-raw has been deprecated in favor of t-out. If the value to render is not wrapped by the \"markup\" function, it will be escaped`);\r\n        }\r\n        const expr = (node.getAttribute(\"t-out\") || node.getAttribute(\"t-raw\"));\r\n        node.removeAttribute(\"t-out\");\r\n        node.removeAttribute(\"t-raw\");\r\n        const tOut = { type: 8 /* TOut */, expr, body: null };\r\n        const ref = node.getAttribute(\"t-ref\");\r\n        node.removeAttribute(\"t-ref\");\r\n        const ast = parseNode(node, ctx);\r\n        if (!ast) {\r\n            return tOut;\r\n        }\r\n        if (ast.type === 2 /* DomNode */) {\r\n            tOut.body = ast.content.length ? ast.content : null;\r\n            return {\r\n                ...ast,\r\n                ref,\r\n                content: [tOut],\r\n            };\r\n        }\r\n        return tOut;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-foreach and t-key\r\n    // -----------------------------------------------------------------------------\r\n    function parseTForEach(node, ctx) {\r\n        if (!node.hasAttribute(\"t-foreach\")) {\r\n            return null;\r\n        }\r\n        const html = node.outerHTML;\r\n        const collection = node.getAttribute(\"t-foreach\");\r\n        node.removeAttribute(\"t-foreach\");\r\n        const elem = node.getAttribute(\"t-as\") || \"\";\r\n        node.removeAttribute(\"t-as\");\r\n        const key = node.getAttribute(\"t-key\");\r\n        if (!key) {\r\n            throw new OwlError(`\"Directive t-foreach should always be used with a t-key!\" (expression: t-foreach=\"${collection}\" t-as=\"${elem}\")`);\r\n        }\r\n        node.removeAttribute(\"t-key\");\r\n        const memo = node.getAttribute(\"t-memo\") || \"\";\r\n        node.removeAttribute(\"t-memo\");\r\n        const body = parseNode(node, ctx);\r\n        if (!body) {\r\n            return null;\r\n        }\r\n        const hasNoTCall = !html.includes(\"t-call\");\r\n        const hasNoFirst = hasNoTCall && !html.includes(`${elem}_first`);\r\n        const hasNoLast = hasNoTCall && !html.includes(`${elem}_last`);\r\n        const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);\r\n        const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);\r\n        return {\r\n            type: 9 /* TForEach */,\r\n            collection,\r\n            elem,\r\n            body,\r\n            memo,\r\n            key,\r\n            hasNoFirst,\r\n            hasNoLast,\r\n            hasNoIndex,\r\n            hasNoValue,\r\n        };\r\n    }\r\n    function parseTKey(node, ctx) {\r\n        if (!node.hasAttribute(\"t-key\")) {\r\n            return null;\r\n        }\r\n        const key = node.getAttribute(\"t-key\");\r\n        node.removeAttribute(\"t-key\");\r\n        const content = parseNode(node, ctx);\r\n        if (!content) {\r\n            return null;\r\n        }\r\n        const ast = {\r\n            type: 10 /* TKey */,\r\n            expr: key,\r\n            content,\r\n        };\r\n        if (content.hasNoRepresentation) {\r\n            ast.hasNoRepresentation = true;\r\n        }\r\n        return ast;\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-call\r\n    // -----------------------------------------------------------------------------\r\n    function parseTCall(node, ctx) {\r\n        if (!node.hasAttribute(\"t-call\")) {\r\n            return null;\r\n        }\r\n        const subTemplate = node.getAttribute(\"t-call\");\r\n        const context = node.getAttribute(\"t-call-context\");\r\n        node.removeAttribute(\"t-call\");\r\n        node.removeAttribute(\"t-call-context\");\r\n        if (node.tagName !== \"t\") {\r\n            const ast = parseNode(node, ctx);\r\n            const tcall = { type: 7 /* TCall */, name: subTemplate, body: null, context };\r\n            if (ast && ast.type === 2 /* DomNode */) {\r\n                ast.content = [tcall];\r\n                return ast;\r\n            }\r\n            if (ast && ast.type === 11 /* TComponent */) {\r\n                return {\r\n                    ...ast,\r\n                    slots: {\r\n                        default: {\r\n                            content: tcall,\r\n                            scope: null,\r\n                            on: null,\r\n                            attrs: null,\r\n                            attrsTranslationCtx: null,\r\n                        },\r\n                    },\r\n                };\r\n            }\r\n        }\r\n        const body = parseChildren(node, ctx);\r\n        return {\r\n            type: 7 /* TCall */,\r\n            name: subTemplate,\r\n            body: body.length ? body : null,\r\n            context,\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-call-block\r\n    // -----------------------------------------------------------------------------\r\n    function parseTCallBlock(node, ctx) {\r\n        if (!node.hasAttribute(\"t-call-block\")) {\r\n            return null;\r\n        }\r\n        const name = node.getAttribute(\"t-call-block\");\r\n        return {\r\n            type: 15 /* TCallBlock */,\r\n            name,\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-if\r\n    // -----------------------------------------------------------------------------\r\n    function parseTIf(node, ctx) {\r\n        if (!node.hasAttribute(\"t-if\")) {\r\n            return null;\r\n        }\r\n        const condition = node.getAttribute(\"t-if\");\r\n        node.removeAttribute(\"t-if\");\r\n        const content = parseNode(node, ctx) || { type: 0 /* Text */, value: \"\" };\r\n        let nextElement = node.nextElementSibling;\r\n        // t-elifs\r\n        const tElifs = [];\r\n        while (nextElement && nextElement.hasAttribute(\"t-elif\")) {\r\n            const condition = nextElement.getAttribute(\"t-elif\");\r\n            nextElement.removeAttribute(\"t-elif\");\r\n            const tElif = parseNode(nextElement, ctx);\r\n            const next = nextElement.nextElementSibling;\r\n            nextElement.remove();\r\n            nextElement = next;\r\n            if (tElif) {\r\n                tElifs.push({ condition, content: tElif });\r\n            }\r\n        }\r\n        // t-else\r\n        let tElse = null;\r\n        if (nextElement && nextElement.hasAttribute(\"t-else\")) {\r\n            nextElement.removeAttribute(\"t-else\");\r\n            tElse = parseNode(nextElement, ctx);\r\n            nextElement.remove();\r\n        }\r\n        return {\r\n            type: 5 /* TIf */,\r\n            condition,\r\n            content,\r\n            tElif: tElifs.length ? tElifs : null,\r\n            tElse,\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // t-set directive\r\n    // -----------------------------------------------------------------------------\r\n    function parseTSetNode(node, ctx) {\r\n        if (!node.hasAttribute(\"t-set\")) {\r\n            return null;\r\n        }\r\n        const name = node.getAttribute(\"t-set\");\r\n        const value = node.getAttribute(\"t-value\") || null;\r\n        const defaultValue = node.innerHTML === node.textContent ? node.textContent || null : null;\r\n        let body = null;\r\n        if (node.textContent !== node.innerHTML) {\r\n            body = parseChildren(node, ctx);\r\n        }\r\n        return { type: 6 /* TSet */, name, value, defaultValue, body, hasNoRepresentation: true };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Components\r\n    // -----------------------------------------------------------------------------\r\n    // Error messages when trying to use an unsupported directive on a component\r\n    const directiveErrorMap = new Map([\r\n        [\r\n            \"t-ref\",\r\n            \"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.\",\r\n        ],\r\n        [\"t-att\", \"t-att makes no sense on component: props are already treated as expressions\"],\r\n        [\r\n            \"t-attf\",\r\n            \"t-attf is not supported on components: use template strings for string interpolation in props\",\r\n        ],\r\n    ]);\r\n    function parseComponent(node, ctx) {\r\n        let name = node.tagName;\r\n        const firstLetter = name[0];\r\n        let isDynamic = node.hasAttribute(\"t-component\");\r\n        if (isDynamic && name !== \"t\") {\r\n            throw new OwlError(`Directive 't-component' can only be used on <t> nodes (used on a <${name}>)`);\r\n        }\r\n        if (!(firstLetter === firstLetter.toUpperCase() || isDynamic)) {\r\n            return null;\r\n        }\r\n        if (isDynamic) {\r\n            name = node.getAttribute(\"t-component\");\r\n            node.removeAttribute(\"t-component\");\r\n        }\r\n        const dynamicProps = node.getAttribute(\"t-props\");\r\n        node.removeAttribute(\"t-props\");\r\n        const defaultSlotScope = node.getAttribute(\"t-slot-scope\");\r\n        node.removeAttribute(\"t-slot-scope\");\r\n        let on = null;\r\n        let props = null;\r\n        let propsTranslationCtx = null;\r\n        for (let name of node.getAttributeNames()) {\r\n            const value = node.getAttribute(name);\r\n            if (name.startsWith(\"t-translation-context-\")) {\r\n                const attrName = name.slice(22);\r\n                propsTranslationCtx = propsTranslationCtx || {};\r\n                propsTranslationCtx[attrName] = value;\r\n            }\r\n            else if (name.startsWith(\"t-\")) {\r\n                if (name.startsWith(\"t-on-\")) {\r\n                    on = on || {};\r\n                    on[name.slice(5)] = value;\r\n                }\r\n                else {\r\n                    const message = directiveErrorMap.get(name.split(\"-\").slice(0, 2).join(\"-\"));\r\n                    throw new OwlError(message || `unsupported directive on Component: ${name}`);\r\n                }\r\n            }\r\n            else {\r\n                props = props || {};\r\n                props[name] = value;\r\n            }\r\n        }\r\n        let slots = null;\r\n        if (node.hasChildNodes()) {\r\n            const clone = node.cloneNode(true);\r\n            // named slots\r\n            const slotNodes = Array.from(clone.querySelectorAll(\"[t-set-slot]\"));\r\n            for (let slotNode of slotNodes) {\r\n                if (slotNode.tagName !== \"t\") {\r\n                    throw new OwlError(`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`);\r\n                }\r\n                const name = slotNode.getAttribute(\"t-set-slot\");\r\n                // check if this is defined in a sub component (in which case it should\r\n                // be ignored)\r\n                let el = slotNode.parentElement;\r\n                let isInSubComponent = false;\r\n                while (el && el !== clone) {\r\n                    if (el.hasAttribute(\"t-component\") || el.tagName[0] === el.tagName[0].toUpperCase()) {\r\n                        isInSubComponent = true;\r\n                        break;\r\n                    }\r\n                    el = el.parentElement;\r\n                }\r\n                if (isInSubComponent || !el) {\r\n                    continue;\r\n                }\r\n                slotNode.removeAttribute(\"t-set-slot\");\r\n                slotNode.remove();\r\n                const slotAst = parseNode(slotNode, ctx);\r\n                let on = null;\r\n                let attrs = null;\r\n                let attrsTranslationCtx = null;\r\n                let scope = null;\r\n                for (let attributeName of slotNode.getAttributeNames()) {\r\n                    const value = slotNode.getAttribute(attributeName);\r\n                    if (attributeName === \"t-slot-scope\") {\r\n                        scope = value;\r\n                        continue;\r\n                    }\r\n                    else if (attributeName.startsWith(\"t-translation-context-\")) {\r\n                        const attrName = attributeName.slice(22);\r\n                        attrsTranslationCtx = attrsTranslationCtx || {};\r\n                        attrsTranslationCtx[attrName] = value;\r\n                    }\r\n                    else if (attributeName.startsWith(\"t-on-\")) {\r\n                        on = on || {};\r\n                        on[attributeName.slice(5)] = value;\r\n                    }\r\n                    else {\r\n                        attrs = attrs || {};\r\n                        attrs[attributeName] = value;\r\n                    }\r\n                }\r\n                slots = slots || {};\r\n                slots[name] = { content: slotAst, on, attrs, attrsTranslationCtx, scope };\r\n            }\r\n            // default slot\r\n            const defaultContent = parseChildNodes(clone, ctx);\r\n            slots = slots || {};\r\n            // t-set-slot=\"default\" has priority over content\r\n            if (defaultContent && !slots.default) {\r\n                slots.default = {\r\n                    content: defaultContent,\r\n                    on,\r\n                    attrs: null,\r\n                    attrsTranslationCtx: null,\r\n                    scope: defaultSlotScope,\r\n                };\r\n            }\r\n        }\r\n        return {\r\n            type: 11 /* TComponent */,\r\n            name,\r\n            isDynamic,\r\n            dynamicProps,\r\n            props,\r\n            propsTranslationCtx,\r\n            slots,\r\n            on,\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Slots\r\n    // -----------------------------------------------------------------------------\r\n    function parseTSlot(node, ctx) {\r\n        if (!node.hasAttribute(\"t-slot\")) {\r\n            return null;\r\n        }\r\n        const name = node.getAttribute(\"t-slot\");\r\n        node.removeAttribute(\"t-slot\");\r\n        let attrs = null;\r\n        let attrsTranslationCtx = null;\r\n        let on = null;\r\n        for (let attributeName of node.getAttributeNames()) {\r\n            const value = node.getAttribute(attributeName);\r\n            if (attributeName.startsWith(\"t-on-\")) {\r\n                on = on || {};\r\n                on[attributeName.slice(5)] = value;\r\n            }\r\n            else if (attributeName.startsWith(\"t-translation-context-\")) {\r\n                const attrName = attributeName.slice(22);\r\n                attrsTranslationCtx = attrsTranslationCtx || {};\r\n                attrsTranslationCtx[attrName] = value;\r\n            }\r\n            else {\r\n                attrs = attrs || {};\r\n                attrs[attributeName] = value;\r\n            }\r\n        }\r\n        return {\r\n            type: 14 /* TSlot */,\r\n            name,\r\n            attrs,\r\n            attrsTranslationCtx,\r\n            on,\r\n            defaultContent: parseChildNodes(node, ctx),\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Translation\r\n    // -----------------------------------------------------------------------------\r\n    function wrapInTTranslationAST(r) {\r\n        const ast = { type: 16 /* TTranslation */, content: r };\r\n        if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {\r\n            ast.hasNoRepresentation = true;\r\n        }\r\n        return ast;\r\n    }\r\n    function parseTTranslation(node, ctx) {\r\n        if (node.getAttribute(\"t-translation\") !== \"off\") {\r\n            return null;\r\n        }\r\n        node.removeAttribute(\"t-translation\");\r\n        const result = parseNode(node, ctx);\r\n        if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* Multi */) {\r\n            const children = result.content.map(wrapInTTranslationAST);\r\n            return makeASTMulti(children);\r\n        }\r\n        return wrapInTTranslationAST(result);\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Translation Context\r\n    // -----------------------------------------------------------------------------\r\n    function wrapInTTranslationContextAST(r, translationCtx) {\r\n        const ast = {\r\n            type: 17 /* TTranslationContext */,\r\n            content: r,\r\n            translationCtx,\r\n        };\r\n        if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {\r\n            ast.hasNoRepresentation = true;\r\n        }\r\n        return ast;\r\n    }\r\n    function parseTTranslationContext(node, ctx) {\r\n        const translationCtx = node.getAttribute(\"t-translation-context\");\r\n        if (!translationCtx) {\r\n            return null;\r\n        }\r\n        node.removeAttribute(\"t-translation-context\");\r\n        const result = parseNode(node, ctx);\r\n        if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* Multi */) {\r\n            const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));\r\n            return makeASTMulti(children);\r\n        }\r\n        return wrapInTTranslationContextAST(result, translationCtx);\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // Portal\r\n    // -----------------------------------------------------------------------------\r\n    function parseTPortal(node, ctx) {\r\n        if (!node.hasAttribute(\"t-portal\")) {\r\n            return null;\r\n        }\r\n        const target = node.getAttribute(\"t-portal\");\r\n        node.removeAttribute(\"t-portal\");\r\n        const content = parseNode(node, ctx);\r\n        if (!content) {\r\n            return {\r\n                type: 0 /* Text */,\r\n                value: \"\",\r\n            };\r\n        }\r\n        return {\r\n            type: 18 /* TPortal */,\r\n            target,\r\n            content,\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // helpers\r\n    // -----------------------------------------------------------------------------\r\n    /**\r\n     * Parse all the child nodes of a given node and return a list of ast elements\r\n     */\r\n    function parseChildren(node, ctx) {\r\n        const children = [];\r\n        for (let child of node.childNodes) {\r\n            const childAst = parseNode(child, ctx);\r\n            if (childAst) {\r\n                if (childAst.type === 3 /* Multi */) {\r\n                    children.push(...childAst.content);\r\n                }\r\n                else {\r\n                    children.push(childAst);\r\n                }\r\n            }\r\n        }\r\n        return children;\r\n    }\r\n    function makeASTMulti(children) {\r\n        const ast = { type: 3 /* Multi */, content: children };\r\n        if (children.every((c) => c.hasNoRepresentation)) {\r\n            ast.hasNoRepresentation = true;\r\n        }\r\n        return ast;\r\n    }\r\n    /**\r\n     * Parse all the child nodes of a given node and return an ast if possible.\r\n     * In the case there are multiple children, they are wrapped in a astmulti.\r\n     */\r\n    function parseChildNodes(node, ctx) {\r\n        const children = parseChildren(node, ctx);\r\n        switch (children.length) {\r\n            case 0:\r\n                return null;\r\n            case 1:\r\n                return children[0];\r\n            default:\r\n                return makeASTMulti(children);\r\n        }\r\n    }\r\n    /**\r\n     * Normalizes the content of an Element so that t-if/t-elif/t-else directives\r\n     * immediately follow one another (by removing empty text nodes or comments).\r\n     * Throws an error when a conditional branching statement is malformed. This\r\n     * function modifies the Element in place.\r\n     *\r\n     * @param el the element containing the tree that should be normalized\r\n     */\r\n    function normalizeTIf(el) {\r\n        let tbranch = el.querySelectorAll(\"[t-elif], [t-else]\");\r\n        for (let i = 0, ilen = tbranch.length; i < ilen; i++) {\r\n            let node = tbranch[i];\r\n            let prevElem = node.previousElementSibling;\r\n            let pattr = (name) => prevElem.getAttribute(name);\r\n            let nattr = (name) => +!!node.getAttribute(name);\r\n            if (prevElem && (pattr(\"t-if\") || pattr(\"t-elif\"))) {\r\n                if (pattr(\"t-foreach\")) {\r\n                    throw new OwlError(\"t-if cannot stay at the same level as t-foreach when using t-elif or t-else\");\r\n                }\r\n                if ([\"t-if\", \"t-elif\", \"t-else\"].map(nattr).reduce(function (a, b) {\r\n                    return a + b;\r\n                }) > 1) {\r\n                    throw new OwlError(\"Only one conditional branching directive is allowed per node\");\r\n                }\r\n                // All text (with only spaces) and comment nodes (nodeType 8) between\r\n                // branch nodes are removed\r\n                let textNode;\r\n                while ((textNode = node.previousSibling) !== prevElem) {\r\n                    if (textNode.nodeValue.trim().length && textNode.nodeType !== 8) {\r\n                        throw new OwlError(\"text is not allowed between branching directives\");\r\n                    }\r\n                    textNode.remove();\r\n                }\r\n            }\r\n            else {\r\n                throw new OwlError(\"t-elif and t-else directives must be preceded by a t-if or t-elif directive\");\r\n            }\r\n        }\r\n    }\r\n    /**\r\n     * Normalizes the content of an Element so that t-esc directives on components\r\n     * are removed and instead places a <t t-esc=\"\"> as the default slot of the\r\n     * component. Also throws if the component already has content. This function\r\n     * modifies the Element in place.\r\n     *\r\n     * @param el the element containing the tree that should be normalized\r\n     */\r\n    function normalizeTEscTOut(el) {\r\n        for (const d of [\"t-esc\", \"t-out\"]) {\r\n            const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute(\"t-component\"));\r\n            for (const el of elements) {\r\n                if (el.childNodes.length) {\r\n                    throw new OwlError(`Cannot have ${d} on a component that already has content`);\r\n                }\r\n                const value = el.getAttribute(d);\r\n                el.removeAttribute(d);\r\n                const t = el.ownerDocument.createElement(\"t\");\r\n                if (value != null) {\r\n                    t.setAttribute(d, value);\r\n                }\r\n                el.appendChild(t);\r\n            }\r\n        }\r\n    }\r\n    /**\r\n     * Normalizes the tree inside a given element and do some preliminary validation\r\n     * on it. This function modifies the Element in place.\r\n     *\r\n     * @param el the element containing the tree that should be normalized\r\n     */\r\n    function normalizeXML(el) {\r\n        normalizeTIf(el);\r\n        normalizeTEscTOut(el);\r\n    }\r\n\r\n    function compile(template, options = {\r\n        hasGlobalValues: false,\r\n    }) {\r\n        // parsing\r\n        const ast = parse(template, options.customDirectives);\r\n        // some work\r\n        const hasSafeContext = template instanceof Node\r\n            ? !(template instanceof Element) || template.querySelector(\"[t-set], [t-call]\") === null\r\n            : !template.includes(\"t-set\") && !template.includes(\"t-call\");\r\n        // code generation\r\n        const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });\r\n        const code = codeGenerator.generateCode();\r\n        // template function\r\n        try {\r\n            return new Function(\"app, bdom, helpers\", code);\r\n        }\r\n        catch (originalError) {\r\n            const { name } = options;\r\n            const nameStr = name ? `template \"${name}\"` : \"anonymous template\";\r\n            const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\\n\\ngenerated code:\\nfunction(app, bdom, helpers) {\\n${code}\\n}`);\r\n            err.cause = originalError;\r\n            throw err;\r\n        }\r\n    }\r\n\r\n    // do not modify manually. This file is generated by the release script.\r\n    const version = \"2.8.1\";\r\n\r\n    // -----------------------------------------------------------------------------\r\n    //  Scheduler\r\n    // -----------------------------------------------------------------------------\r\n    class Scheduler {\r\n        constructor() {\r\n            this.tasks = new Set();\r\n            this.frame = 0;\r\n            this.delayedRenders = [];\r\n            this.cancelledNodes = new Set();\r\n            this.processing = false;\r\n            this.requestAnimationFrame = Scheduler.requestAnimationFrame;\r\n        }\r\n        addFiber(fiber) {\r\n            this.tasks.add(fiber.root);\r\n        }\r\n        scheduleDestroy(node) {\r\n            this.cancelledNodes.add(node);\r\n            if (this.frame === 0) {\r\n                this.frame = this.requestAnimationFrame(() => this.processTasks());\r\n            }\r\n        }\r\n        /**\r\n         * Process all current tasks. This only applies to the fibers that are ready.\r\n         * Other tasks are left unchanged.\r\n         */\r\n        flush() {\r\n            if (this.delayedRenders.length) {\r\n                let renders = this.delayedRenders;\r\n                this.delayedRenders = [];\r\n                for (let f of renders) {\r\n                    if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {\r\n                        f.render();\r\n                    }\r\n                }\r\n            }\r\n            if (this.frame === 0) {\r\n                this.frame = this.requestAnimationFrame(() => this.processTasks());\r\n            }\r\n        }\r\n        processTasks() {\r\n            if (this.processing) {\r\n                return;\r\n            }\r\n            this.processing = true;\r\n            this.frame = 0;\r\n            for (let node of this.cancelledNodes) {\r\n                node._destroy();\r\n            }\r\n            this.cancelledNodes.clear();\r\n            for (let task of this.tasks) {\r\n                this.processFiber(task);\r\n            }\r\n            for (let task of this.tasks) {\r\n                if (task.node.status === 3 /* DESTROYED */) {\r\n                    this.tasks.delete(task);\r\n                }\r\n            }\r\n            this.processing = false;\r\n        }\r\n        processFiber(fiber) {\r\n            if (fiber.root !== fiber) {\r\n                this.tasks.delete(fiber);\r\n                return;\r\n            }\r\n            const hasError = fibersInError.has(fiber);\r\n            if (hasError && fiber.counter !== 0) {\r\n                this.tasks.delete(fiber);\r\n                return;\r\n            }\r\n            if (fiber.node.status === 3 /* DESTROYED */) {\r\n                this.tasks.delete(fiber);\r\n                return;\r\n            }\r\n            if (fiber.counter === 0) {\r\n                if (!hasError) {\r\n                    fiber.complete();\r\n                }\r\n                // at this point, the fiber should have been applied to the DOM, so we can\r\n                // remove it from the task list. If it is not the case, it means that there\r\n                // was an error and an error handler triggered a new rendering that recycled\r\n                // the fiber, so in that case, we actually want to keep the fiber around,\r\n                // otherwise it will just be ignored.\r\n                if (fiber.appliedToDom) {\r\n                    this.tasks.delete(fiber);\r\n                }\r\n            }\r\n        }\r\n    }\r\n    // capture the value of requestAnimationFrame as soon as possible, to avoid\r\n    // interactions with other code, such as test frameworks that override them\r\n    Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);\r\n\r\n    let hasBeenLogged = false;\r\n    const apps = new Set();\r\n    window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = { apps, Fiber, RootFiber, toRaw, reactive });\r\n    class App extends TemplateSet {\r\n        constructor(Root, config = {}) {\r\n            super(config);\r\n            this.scheduler = new Scheduler();\r\n            this.subRoots = new Set();\r\n            this.root = null;\r\n            this.name = config.name || \"\";\r\n            this.Root = Root;\r\n            apps.add(this);\r\n            if (config.test) {\r\n                this.dev = true;\r\n            }\r\n            this.warnIfNoStaticProps = config.warnIfNoStaticProps || false;\r\n            if (this.dev && !config.test && !hasBeenLogged) {\r\n                console.info(`Owl is running in 'dev' mode.`);\r\n                hasBeenLogged = true;\r\n            }\r\n            const env = config.env || {};\r\n            const descrs = Object.getOwnPropertyDescriptors(env);\r\n            this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));\r\n            this.props = config.props || {};\r\n        }\r\n        mount(target, options) {\r\n            const root = this.createRoot(this.Root, { props: this.props });\r\n            this.root = root.node;\r\n            this.subRoots.delete(root.node);\r\n            return root.mount(target, options);\r\n        }\r\n        createRoot(Root, config = {}) {\r\n            const props = config.props || {};\r\n            // hack to make sure the sub root get the sub env if necessary. for owl 3,\r\n            // would be nice to rethink the initialization process to make sure that\r\n            // we can create a ComponentNode and give it explicitely the env, instead\r\n            // of looking it up in the app\r\n            const env = this.env;\r\n            if (config.env) {\r\n                this.env = config.env;\r\n            }\r\n            const restore = saveCurrent();\r\n            const node = this.makeNode(Root, props);\r\n            restore();\r\n            if (config.env) {\r\n                this.env = env;\r\n            }\r\n            this.subRoots.add(node);\r\n            return {\r\n                node,\r\n                mount: (target, options) => {\r\n                    App.validateTarget(target);\r\n                    if (this.dev) {\r\n                        validateProps(Root, props, { __owl__: { app: this } });\r\n                    }\r\n                    const prom = this.mountNode(node, target, options);\r\n                    return prom;\r\n                },\r\n                destroy: () => {\r\n                    this.subRoots.delete(node);\r\n                    node.destroy();\r\n                    this.scheduler.processTasks();\r\n                },\r\n            };\r\n        }\r\n        makeNode(Component, props) {\r\n            return new ComponentNode(Component, props, this, null, null);\r\n        }\r\n        mountNode(node, target, options) {\r\n            const promise = new Promise((resolve, reject) => {\r\n                let isResolved = false;\r\n                // manually set a onMounted callback.\r\n                // that way, we are independant from the current node.\r\n                node.mounted.push(() => {\r\n                    resolve(node.component);\r\n                    isResolved = true;\r\n                });\r\n                // Manually add the last resort error handler on the node\r\n                let handlers = nodeErrorHandlers.get(node);\r\n                if (!handlers) {\r\n                    handlers = [];\r\n                    nodeErrorHandlers.set(node, handlers);\r\n                }\r\n                handlers.unshift((e) => {\r\n                    if (!isResolved) {\r\n                        reject(e);\r\n                    }\r\n                    throw e;\r\n                });\r\n            });\r\n            node.mountComponent(target, options);\r\n            return promise;\r\n        }\r\n        destroy() {\r\n            if (this.root) {\r\n                for (let subroot of this.subRoots) {\r\n                    subroot.destroy();\r\n                }\r\n                this.root.destroy();\r\n                this.scheduler.processTasks();\r\n            }\r\n            apps.delete(this);\r\n        }\r\n        createComponent(name, isStatic, hasSlotsProp, hasDynamicPropList, propList) {\r\n            const isDynamic = !isStatic;\r\n            let arePropsDifferent;\r\n            const hasNoProp = propList.length === 0;\r\n            if (hasSlotsProp) {\r\n                arePropsDifferent = (_1, _2) => true;\r\n            }\r\n            else if (hasDynamicPropList) {\r\n                arePropsDifferent = function (props1, props2) {\r\n                    for (let k in props1) {\r\n                        if (props1[k] !== props2[k]) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                    return Object.keys(props1).length !== Object.keys(props2).length;\r\n                };\r\n            }\r\n            else if (hasNoProp) {\r\n                arePropsDifferent = (_1, _2) => false;\r\n            }\r\n            else {\r\n                arePropsDifferent = function (props1, props2) {\r\n                    for (let p of propList) {\r\n                        if (props1[p] !== props2[p]) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                    return false;\r\n                };\r\n            }\r\n            const updateAndRender = ComponentNode.prototype.updateAndRender;\r\n            const initiateRender = ComponentNode.prototype.initiateRender;\r\n            return (props, key, ctx, parent, C) => {\r\n                let children = ctx.children;\r\n                let node = children[key];\r\n                if (isDynamic && node && node.component.constructor !== C) {\r\n                    node = undefined;\r\n                }\r\n                const parentFiber = ctx.fiber;\r\n                if (node) {\r\n                    if (arePropsDifferent(node.props, props) || parentFiber.deep || node.forceNextRender) {\r\n                        node.forceNextRender = false;\r\n                        updateAndRender.call(node, props, parentFiber);\r\n                    }\r\n                }\r\n                else {\r\n                    // new component\r\n                    if (isStatic) {\r\n                        const components = parent.constructor.components;\r\n                        if (!components) {\r\n                            throw new OwlError(`Cannot find the definition of component \"${name}\", missing static components key in parent`);\r\n                        }\r\n                        C = components[name];\r\n                        if (!C) {\r\n                            throw new OwlError(`Cannot find the definition of component \"${name}\"`);\r\n                        }\r\n                        else if (!(C.prototype instanceof Component)) {\r\n                            throw new OwlError(`\"${name}\" is not a Component. It must inherit from the Component class`);\r\n                        }\r\n                    }\r\n                    node = new ComponentNode(C, props, this, ctx, key);\r\n                    children[key] = node;\r\n                    initiateRender.call(node, new Fiber(node, parentFiber));\r\n                }\r\n                parentFiber.childrenMap[key] = node;\r\n                return node;\r\n            };\r\n        }\r\n        handleError(...args) {\r\n            return handleError(...args);\r\n        }\r\n    }\r\n    App.validateTarget = validateTarget;\r\n    App.apps = apps;\r\n    App.version = version;\r\n    async function mount(C, target, config = {}) {\r\n        return new App(C, config).mount(target, config);\r\n    }\r\n\r\n    const mainEventHandler = (data, ev, currentTarget) => {\r\n        const { data: _data, modifiers } = filterOutModifiersFromData(data);\r\n        data = _data;\r\n        let stopped = false;\r\n        if (modifiers.length) {\r\n            let selfMode = false;\r\n            const isSelf = ev.target === currentTarget;\r\n            for (const mod of modifiers) {\r\n                switch (mod) {\r\n                    case \"self\":\r\n                        selfMode = true;\r\n                        if (isSelf) {\r\n                            continue;\r\n                        }\r\n                        else {\r\n                            return stopped;\r\n                        }\r\n                    case \"prevent\":\r\n                        if ((selfMode && isSelf) || !selfMode)\r\n                            ev.preventDefault();\r\n                        continue;\r\n                    case \"stop\":\r\n                        if ((selfMode && isSelf) || !selfMode)\r\n                            ev.stopPropagation();\r\n                        stopped = true;\r\n                        continue;\r\n                }\r\n            }\r\n        }\r\n        // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0\r\n        // We check this rather than data[0] being truthy (or typeof function) so that it crashes\r\n        // as expected when there is a handler expression that evaluates to a falsy value\r\n        if (Object.hasOwnProperty.call(data, 0)) {\r\n            const handler = data[0];\r\n            if (typeof handler !== \"function\") {\r\n                throw new OwlError(`Invalid handler (expected a function, received: '${handler}')`);\r\n            }\r\n            let node = data[1] ? data[1].__owl__ : null;\r\n            if (node ? node.status === 1 /* MOUNTED */ : true) {\r\n                handler.call(node ? node.component : null, ev);\r\n            }\r\n        }\r\n        return stopped;\r\n    };\r\n\r\n    function status(component) {\r\n        switch (component.__owl__.status) {\r\n            case 0 /* NEW */:\r\n                return \"new\";\r\n            case 2 /* CANCELLED */:\r\n                return \"cancelled\";\r\n            case 1 /* MOUNTED */:\r\n                return \"mounted\";\r\n            case 3 /* DESTROYED */:\r\n                return \"destroyed\";\r\n        }\r\n    }\r\n\r\n    // -----------------------------------------------------------------------------\r\n    // useRef\r\n    // -----------------------------------------------------------------------------\r\n    /**\r\n     * The purpose of this hook is to allow components to get a reference to a sub\r\n     * html node or component.\r\n     */\r\n    function useRef(name) {\r\n        const node = getCurrent();\r\n        const refs = node.refs;\r\n        return {\r\n            get el() {\r\n                const el = refs[name];\r\n                return inOwnerDocument(el) ? el : null;\r\n            },\r\n        };\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // useEnv and useSubEnv\r\n    // -----------------------------------------------------------------------------\r\n    /**\r\n     * This hook is useful as a building block for some customized hooks, that may\r\n     * need a reference to the env of the component calling them.\r\n     */\r\n    function useEnv() {\r\n        return getCurrent().component.env;\r\n    }\r\n    function extendEnv(currentEnv, extension) {\r\n        const env = Object.create(currentEnv);\r\n        const descrs = Object.getOwnPropertyDescriptors(extension);\r\n        return Object.freeze(Object.defineProperties(env, descrs));\r\n    }\r\n    /**\r\n     * This hook is a simple way to let components use a sub environment.  Note that\r\n     * like for all hooks, it is important that this is only called in the\r\n     * constructor method.\r\n     */\r\n    function useSubEnv(envExtension) {\r\n        const node = getCurrent();\r\n        node.component.env = extendEnv(node.component.env, envExtension);\r\n        useChildSubEnv(envExtension);\r\n    }\r\n    function useChildSubEnv(envExtension) {\r\n        const node = getCurrent();\r\n        node.childEnv = extendEnv(node.childEnv, envExtension);\r\n    }\r\n    /**\r\n     * This hook will run a callback when a component is mounted and patched, and\r\n     * will run a cleanup function before patching and before unmounting the\r\n     * the component.\r\n     *\r\n     * @template T\r\n     * @param {Effect<T>} effect the effect to run on component mount and/or patch\r\n     * @param {()=>[...T]} [computeDependencies=()=>[NaN]] a callback to compute\r\n     *      dependencies that will decide if the effect needs to be cleaned up and\r\n     *      run again. If the dependencies did not change, the effect will not run\r\n     *      again. The default value returns an array containing only NaN because\r\n     *      NaN !== NaN, which will cause the effect to rerun on every patch.\r\n     */\r\n    function useEffect(effect, computeDependencies = () => [NaN]) {\r\n        let cleanup;\r\n        let dependencies;\r\n        onMounted(() => {\r\n            dependencies = computeDependencies();\r\n            cleanup = effect(...dependencies);\r\n        });\r\n        onPatched(() => {\r\n            const newDeps = computeDependencies();\r\n            const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);\r\n            if (shouldReapply) {\r\n                dependencies = newDeps;\r\n                if (cleanup) {\r\n                    cleanup();\r\n                }\r\n                cleanup = effect(...dependencies);\r\n            }\r\n        });\r\n        onWillUnmount(() => cleanup && cleanup());\r\n    }\r\n    // -----------------------------------------------------------------------------\r\n    // useExternalListener\r\n    // -----------------------------------------------------------------------------\r\n    /**\r\n     * When a component needs to listen to DOM Events on element(s) that are not\r\n     * part of his hierarchy, we can use the `useExternalListener` hook.\r\n     * It will correctly add and remove the event listener, whenever the\r\n     * component is mounted and unmounted.\r\n     *\r\n     * Example:\r\n     *  a menu needs to listen to the click on window to be closed automatically\r\n     *\r\n     * Usage:\r\n     *  in the constructor of the OWL component that needs to be notified,\r\n     *  `useExternalListener(window, 'click', this._doSomething);`\r\n     * */\r\n    function useExternalListener(target, eventName, handler, eventParams) {\r\n        const node = getCurrent();\r\n        const boundHandler = handler.bind(node.component);\r\n        onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));\r\n        onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));\r\n    }\r\n\r\n    config.shouldNormalizeDom = false;\r\n    config.mainEventHandler = mainEventHandler;\r\n    const blockDom = {\r\n        config,\r\n        // bdom entry points\r\n        mount: mount$1,\r\n        patch,\r\n        remove,\r\n        // bdom block types\r\n        list,\r\n        multi,\r\n        text,\r\n        toggler,\r\n        createBlock,\r\n        html,\r\n        comment,\r\n    };\r\n    const __info__ = {\r\n        version: App.version,\r\n    };\r\n\r\n    TemplateSet.prototype._compileTemplate = function _compileTemplate(name, template) {\r\n        return compile(template, {\r\n            name,\r\n            dev: this.dev,\r\n            translateFn: this.translateFn,\r\n            translatableAttributes: this.translatableAttributes,\r\n            customDirectives: this.customDirectives,\r\n            hasGlobalValues: this.hasGlobalValues,\r\n        });\r\n    };\r\n\r\n    exports.App = App;\r\n    exports.Component = Component;\r\n    exports.EventBus = EventBus;\r\n    exports.OwlError = OwlError;\r\n    exports.__info__ = __info__;\r\n    exports.batched = batched;\r\n    exports.blockDom = blockDom;\r\n    exports.htmlEscape = htmlEscape;\r\n    exports.loadFile = loadFile;\r\n    exports.markRaw = markRaw;\r\n    exports.markup = markup;\r\n    exports.mount = mount;\r\n    exports.onError = onError;\r\n    exports.onMounted = onMounted;\r\n    exports.onPatched = onPatched;\r\n    exports.onRendered = onRendered;\r\n    exports.onWillDestroy = onWillDestroy;\r\n    exports.onWillPatch = onWillPatch;\r\n    exports.onWillRender = onWillRender;\r\n    exports.onWillStart = onWillStart;\r\n    exports.onWillUnmount = onWillUnmount;\r\n    exports.onWillUpdateProps = onWillUpdateProps;\r\n    exports.reactive = reactive;\r\n    exports.status = status;\r\n    exports.toRaw = toRaw;\r\n    exports.useChildSubEnv = useChildSubEnv;\r\n    exports.useComponent = useComponent;\r\n    exports.useEffect = useEffect;\r\n    exports.useEnv = useEnv;\r\n    exports.useExternalListener = useExternalListener;\r\n    exports.useRef = useRef;\r\n    exports.useState = useState;\r\n    exports.useSubEnv = useSubEnv;\r\n    exports.validate = validate;\r\n    exports.validateType = validateType;\r\n    exports.whenReady = whenReady;\r\n    exports.xml = xml;\r\n\r\n    Object.defineProperty(exports, '__esModule', { value: true });\r\n\r\n\r\n    __info__.date = '2025-09-23T07:17:45.055Z';\r\n    __info__.hash = '5211116';\r\n    __info__.url = 'https://github.com/odoo/owl';\r\n\r\n\r\n})(this.owl = this.owl || {});\r\n", "odoo.define(\"@odoo/owl\", [], function () {\n    \"use strict\";\n\n    return owl;\n});\n", "/*!\n * jQuery JavaScript Library v3.6.3\n * https://jquery.com/\n *\n * Includes Sizzle.js\n * https://sizzlejs.com/\n *\n * Copyright OpenJS Foundation and other contributors\n * Released under the MIT license\n * https://jquery.org/license\n *\n * Date: 2022-12-20T21:28Z\n */\n( function( global, factory ) {\n\n\t\"use strict\";\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\n\t\t// For CommonJS and CommonJS-like environments where a proper `window`\n\t\t// is present, execute the factory and get jQuery.\n\t\t// For environments that do not have a `window` with a `document`\n\t\t// (such as Node.js), expose a factory as module.exports.\n\t\t// This accentuates the need for the creation of a real `window`.\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket trac-14549 for more info.\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n} )( typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1\n// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode\n// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common\n// enough that all such attempts are guarded in a try block.\n\"use strict\";\n\nvar arr = [];\n\nvar getProto = Object.getPrototypeOf;\n\nvar slice = arr.slice;\n\nvar flat = arr.flat ? function( array ) {\n\treturn arr.flat.call( array );\n} : function( array ) {\n\treturn arr.concat.apply( [], array );\n};\n\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar fnToString = hasOwn.toString;\n\nvar ObjectFunctionString = fnToString.call( Object );\n\nvar support = {};\n\nvar isFunction = function isFunction( obj ) {\n\n\t\t// Support: Chrome <=57, Firefox <=52\n\t\t// In some browsers, typeof returns \"function\" for HTML <object> elements\n\t\t// (i.e., `typeof document.createElement( \"object\" ) === \"function\"`).\n\t\t// We don't want to classify *any* DOM node as a function.\n\t\t// Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5\n\t\t// Plus for old WebKit, typeof returns \"function\" for HTML collections\n\t\t// (e.g., `typeof document.getElementsByTagName(\"div\") === \"function\"`). (gh-4756)\n\t\treturn typeof obj === \"function\" && typeof obj.nodeType !== \"number\" &&\n\t\t\ttypeof obj.item !== \"function\";\n\t};\n\n\nvar isWindow = function isWindow( obj ) {\n\t\treturn obj != null && obj === obj.window;\n\t};\n\n\nvar document = window.document;\n\n\n\n\tvar preservedScriptAttributes = {\n\t\ttype: true,\n\t\tsrc: true,\n\t\tnonce: true,\n\t\tnoModule: true\n\t};\n\n\tfunction DOMEval( code, node, doc ) {\n\t\tdoc = doc || document;\n\n\t\tvar i, val,\n\t\t\tscript = doc.createElement( \"script\" );\n\n\t\tscript.text = code;\n\t\tif ( node ) {\n\t\t\tfor ( i in preservedScriptAttributes ) {\n\n\t\t\t\t// Support: Firefox 64+, Edge 18+\n\t\t\t\t// Some browsers don't support the \"nonce\" property on scripts.\n\t\t\t\t// On the other hand, just using `getAttribute` is not enough as\n\t\t\t\t// the `nonce` attribute is reset to an empty string whenever it\n\t\t\t\t// becomes browsing-context connected.\n\t\t\t\t// See https://github.com/whatwg/html/issues/2369\n\t\t\t\t// See https://html.spec.whatwg.org/#nonce-attributes\n\t\t\t\t// The `node.getAttribute` check was added for the sake of\n\t\t\t\t// `jQuery.globalEval` so that it can fake a nonce-containing node\n\t\t\t\t// via an object.\n\t\t\t\tval = node[ i ] || node.getAttribute && node.getAttribute( i );\n\t\t\t\tif ( val ) {\n\t\t\t\t\tscript.setAttribute( i, val );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdoc.head.appendChild( script ).parentNode.removeChild( script );\n\t}\n\n\nfunction toType( obj ) {\n\tif ( obj == null ) {\n\t\treturn obj + \"\";\n\t}\n\n\t// Support: Android <=2.3 only (functionish RegExp)\n\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\tclass2type[ toString.call( obj ) ] || \"object\" :\n\t\ttypeof obj;\n}\n/* global Symbol */\n// Defining this global in .eslintrc.json would create a danger of using the global\n// unguarded in another place, it seems safer to define global only for this module\n\n\n\nvar\n\tversion = \"3.6.3\",\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init( selector, context );\n\t};\n\njQuery.fn = jQuery.prototype = {\n\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\n\t\t// Return all the elements in a clean array\n\t\tif ( num == null ) {\n\t\t\treturn slice.call( this );\n\t\t}\n\n\t\t// Return just the one element from the set\n\t\treturn num < 0 ? this[ num + this.length ] : this[ num ];\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\teach: function( callback ) {\n\t\treturn jQuery.each( this, callback );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map( this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t} ) );\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teven: function() {\n\t\treturn this.pushStack( jQuery.grep( this, function( _elem, i ) {\n\t\t\treturn ( i + 1 ) % 2;\n\t\t} ) );\n\t},\n\n\todd: function() {\n\t\treturn this.pushStack( jQuery.grep( this, function( _elem, i ) {\n\t\t\treturn i % 2;\n\t\t} ) );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor();\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[ 0 ] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\n\t\t// Skip the boolean and the target\n\t\ttarget = arguments[ i ] || {};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !isFunction( target ) ) {\n\t\ttarget = {};\n\t}\n\n\t// Extend jQuery itself if only one argument is passed\n\tif ( i === length ) {\n\t\ttarget = this;\n\t\ti--;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\n\t\t// Only deal with non-null/undefined values\n\t\tif ( ( options = arguments[ i ] ) != null ) {\n\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent Object.prototype pollution\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( name === \"__proto__\" || target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject( copy ) ||\n\t\t\t\t\t( copyIsArray = Array.isArray( copy ) ) ) ) {\n\t\t\t\t\tsrc = target[ name ];\n\n\t\t\t\t\t// Ensure proper type for the source value\n\t\t\t\t\tif ( copyIsArray && !Array.isArray( src ) ) {\n\t\t\t\t\t\tclone = [];\n\t\t\t\t\t} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {\n\t\t\t\t\t\tclone = {};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src;\n\t\t\t\t\t}\n\t\t\t\t\tcopyIsArray = false;\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend( {\n\n\t// Unique for each copy of jQuery on the page\n\texpando: \"jQuery\" + ( version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\tnoop: function() {},\n\n\tisPlainObject: function( obj ) {\n\t\tvar proto, Ctor;\n\n\t\t// Detect obvious negatives\n\t\t// Use toString instead of jQuery.type to catch host objects\n\t\tif ( !obj || toString.call( obj ) !== \"[object Object]\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tproto = getProto( obj );\n\n\t\t// Objects with no prototype (e.g., `Object.create( null )`) are plain\n\t\tif ( !proto ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Objects with prototype are plain iff they were constructed by a global Object function\n\t\tCtor = hasOwn.call( proto, \"constructor\" ) && proto.constructor;\n\t\treturn typeof Ctor === \"function\" && fnToString.call( Ctor ) === ObjectFunctionString;\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\t// Evaluates a script in a provided context; falls back to the global one\n\t// if not specified.\n\tglobalEval: function( code, options, doc ) {\n\t\tDOMEval( code, { nonce: options && options.nonce }, doc );\n\t},\n\n\teach: function( obj, callback ) {\n\t\tvar length, i = 0;\n\n\t\tif ( isArrayLike( obj ) ) {\n\t\t\tlength = obj.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor ( i in obj ) {\n\t\t\t\tif ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArrayLike( Object( arr ) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpush.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\treturn arr == null ? -1 : indexOf.call( arr, elem, i );\n\t},\n\n\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t// push.apply(_, arraylike) throws on ancient WebKit\n\tmerge: function( first, second ) {\n\t\tvar len = +second.length,\n\t\t\tj = 0,\n\t\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, invert ) {\n\t\tvar callbackInverse,\n\t\t\tmatches = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tcallbackInverse = !callback( elems[ i ], i );\n\t\t\tif ( callbackInverse !== callbackExpect ) {\n\t\t\t\tmatches.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar length, value,\n\t\t\ti = 0,\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif ( isArrayLike( elems ) ) {\n\t\t\tlength = elems.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn flat( ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n} );\n\nif ( typeof Symbol === \"function\" ) {\n\tjQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];\n}\n\n// Populate the class2type map\njQuery.each( \"Boolean Number String Function Array Date RegExp Object Error Symbol\".split( \" \" ),\n\tfunction( _i, name ) {\n\t\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n\t} );\n\nfunction isArrayLike( obj ) {\n\n\t// Support: real iOS 8.2 only (not reproducible in simulator)\n\t// `in` check used to prevent JIT error (gh-2145)\n\t// hasOwn isn't used here due to false negatives\n\t// regarding Nodelist length in IE\n\tvar length = !!obj && \"length\" in obj && obj.length,\n\t\ttype = toType( obj );\n\n\tif ( isFunction( obj ) || isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\treturn type === \"array\" || length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n}\nvar Sizzle =\n/*!\n * Sizzle CSS Selector Engine v2.3.9\n * https://sizzlejs.com/\n *\n * Copyright JS Foundation and other contributors\n * Released under the MIT license\n * https://js.foundation/\n *\n * Date: 2022-12-19\n */\n( function( window ) {\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\ttokenize,\n\tcompile,\n\tselect,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + 1 * new Date(),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tnonnativeSelectorCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// Instance methods\n\thasOwn = ( {} ).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpushNative = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\n\t// Use a stripped-down indexOf as it's faster than native\n\t// https://jsperf.com/thor-indexof-vs-for/5\n\tindexOf = function( list, elem ) {\n\t\tvar i = 0,\n\t\t\tlen = list.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( list[ i ] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|\" +\n\t\t\"ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\n\t// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram\n\tidentifier = \"(?:\\\\\\\\[\\\\da-fA-F]{1,6}\" + whitespace +\n\t\t\"?|\\\\\\\\[^\\\\r\\\\n\\\\f]|[\\\\w-]|[^\\0-\\\\x7f])+\",\n\n\t// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + identifier + \")(?:\" + whitespace +\n\n\t\t// Operator (capture 2)\n\t\t\"*([*^$|!~]?=)\" + whitespace +\n\n\t\t// \"Attribute values must be CSS identifiers [capture 5]\n\t\t// or strings [capture 3 or capture 4]\"\n\t\t\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\" + identifier + \"))|)\" +\n\t\twhitespace + \"*\\\\]\",\n\n\tpseudos = \":(\" + identifier + \")(?:\\\\((\" +\n\n\t\t// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:\n\t\t// 1. quoted (capture 3; capture 4 or capture 5)\n\t\t\"('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|\" +\n\n\t\t// 2. simple (capture 6)\n\t\t\"((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes + \")*)|\" +\n\n\t\t// 3. anything else (capture 2)\n\t\t\".*\" +\n\t\t\")\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trwhitespace = new RegExp( whitespace + \"+\", \"g\" ),\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" +\n\t\twhitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace +\n\t\t\"*\" ),\n\trdescend = new RegExp( whitespace + \"|>\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + identifier + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + identifier + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + identifier + \"|[*])\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" +\n\t\t\twhitespace + \"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" +\n\t\t\twhitespace + \"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace +\n\t\t\t\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" + whitespace +\n\t\t\t\"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trhtml = /HTML$/i,\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\n\t// CSS escapes\n\t// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\[\\\\da-fA-F]{1,6}\" + whitespace + \"?|\\\\\\\\([^\\\\r\\\\n\\\\f])\", \"g\" ),\n\tfunescape = function( escape, nonHex ) {\n\t\tvar high = \"0x\" + escape.slice( 1 ) - 0x10000;\n\n\t\treturn nonHex ?\n\n\t\t\t// Strip the backslash prefix from a non-hex escape sequence\n\t\t\tnonHex :\n\n\t\t\t// Replace a hexadecimal escape sequence with the encoded Unicode code point\n\t\t\t// Support: IE <=11+\n\t\t\t// For values outside the Basic Multilingual Plane (BMP), manually construct a\n\t\t\t// surrogate pair\n\t\t\thigh < 0 ?\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t},\n\n\t// CSS string/identifier serialization\n\t// https://drafts.csswg.org/cssom/#common-serializing-idioms\n\trcssescape = /([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,\n\tfcssescape = function( ch, asCodePoint ) {\n\t\tif ( asCodePoint ) {\n\n\t\t\t// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER\n\t\t\tif ( ch === \"\\0\" ) {\n\t\t\t\treturn \"\\uFFFD\";\n\t\t\t}\n\n\t\t\t// Control characters and (dependent upon position) numbers get escaped as code points\n\t\t\treturn ch.slice( 0, -1 ) + \"\\\\\" +\n\t\t\t\tch.charCodeAt( ch.length - 1 ).toString( 16 ) + \" \";\n\t\t}\n\n\t\t// Other potentially-special ASCII characters get backslash-escaped\n\t\treturn \"\\\\\" + ch;\n\t},\n\n\t// Used for iframes\n\t// See setDocument()\n\t// Removing the function wrapper causes a \"Permission Denied\"\n\t// error in IE\n\tunloadHandler = function() {\n\t\tsetDocument();\n\t},\n\n\tinDisabledFieldset = addCombinator(\n\t\tfunction( elem ) {\n\t\t\treturn elem.disabled === true && elem.nodeName.toLowerCase() === \"fieldset\";\n\t\t},\n\t\t{ dir: \"parentNode\", next: \"legend\" }\n\t);\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t( arr = slice.call( preferredDoc.childNodes ) ),\n\t\tpreferredDoc.childNodes\n\t);\n\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\t// eslint-disable-next-line no-unused-expressions\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpushNative.apply( target, slice.call( els ) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( ( target[ j++ ] = els[ i++ ] ) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar m, i, elem, nid, match, groups, newSelector,\n\t\tnewContext = context && context.ownerDocument,\n\n\t\t// nodeType defaults to 9, since context defaults to document\n\t\tnodeType = context ? context.nodeType : 9;\n\n\tresults = results || [];\n\n\t// Return early from calls with invalid selector or context\n\tif ( typeof selector !== \"string\" || !selector ||\n\t\tnodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {\n\n\t\treturn results;\n\t}\n\n\t// Try to shortcut find operations (as opposed to filters) in HTML documents\n\tif ( !seed ) {\n\t\tsetDocument( context );\n\t\tcontext = context || document;\n\n\t\tif ( documentIsHTML ) {\n\n\t\t\t// If the selector is sufficiently simple, try using a \"get*By*\" DOM method\n\t\t\t// (excepting DocumentFragment context, where the methods don't exist)\n\t\t\tif ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {\n\n\t\t\t\t// ID selector\n\t\t\t\tif ( ( m = match[ 1 ] ) ) {\n\n\t\t\t\t\t// Document context\n\t\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\t\tif ( ( elem = context.getElementById( m ) ) ) {\n\n\t\t\t\t\t\t\t// Support: IE, Opera, Webkit\n\t\t\t\t\t\t\t// TODO: identify versions\n\t\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// Element context\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t// Support: IE, Opera, Webkit\n\t\t\t\t\t\t// TODO: identify versions\n\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\tif ( newContext && ( elem = newContext.getElementById( m ) ) &&\n\t\t\t\t\t\t\tcontains( context, elem ) &&\n\t\t\t\t\t\t\telem.id === m ) {\n\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t// Type selector\n\t\t\t\t} else if ( match[ 2 ] ) {\n\t\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\t\treturn results;\n\n\t\t\t\t// Class selector\n\t\t\t\t} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&\n\t\t\t\t\tcontext.getElementsByClassName ) {\n\n\t\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Take advantage of querySelectorAll\n\t\t\tif ( support.qsa &&\n\t\t\t\t!nonnativeSelectorCache[ selector + \" \" ] &&\n\t\t\t\t( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&\n\n\t\t\t\t// Support: IE 8 only\n\t\t\t\t// Exclude object elements\n\t\t\t\t( nodeType !== 1 || context.nodeName.toLowerCase() !== \"object\" ) ) {\n\n\t\t\t\tnewSelector = selector;\n\t\t\t\tnewContext = context;\n\n\t\t\t\t// qSA considers elements outside a scoping root when evaluating child or\n\t\t\t\t// descendant combinators, which is not what we want.\n\t\t\t\t// In such cases, we work around the behavior by prefixing every selector in the\n\t\t\t\t// list with an ID selector referencing the scope context.\n\t\t\t\t// The technique has to be used as well when a leading combinator is used\n\t\t\t\t// as such selectors are not recognized by querySelectorAll.\n\t\t\t\t// Thanks to Andrew Dupont for this technique.\n\t\t\t\tif ( nodeType === 1 &&\n\t\t\t\t\t( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {\n\n\t\t\t\t\t// Expand context for sibling selectors\n\t\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) ||\n\t\t\t\t\t\tcontext;\n\n\t\t\t\t\t// We can use :scope instead of the ID hack if the browser\n\t\t\t\t\t// supports it & if we're not changing the context.\n\t\t\t\t\tif ( newContext !== context || !support.scope ) {\n\n\t\t\t\t\t\t// Capture the context ID, setting it first if necessary\n\t\t\t\t\t\tif ( ( nid = context.getAttribute( \"id\" ) ) ) {\n\t\t\t\t\t\t\tnid = nid.replace( rcssescape, fcssescape );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontext.setAttribute( \"id\", ( nid = expando ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prefix every selector in the list\n\t\t\t\t\tgroups = tokenize( selector );\n\t\t\t\t\ti = groups.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tgroups[ i ] = ( nid ? \"#\" + nid : \":scope\" ) + \" \" +\n\t\t\t\t\t\t\ttoSelector( groups[ i ] );\n\t\t\t\t\t}\n\t\t\t\t\tnewSelector = groups.join( \",\" );\n\t\t\t\t}\n\n\t\t\t\ttry {\n\n\t\t\t\t\t// `qSA` may not throw for unrecognized parts using forgiving parsing:\n\t\t\t\t\t// https://drafts.csswg.org/selectors/#forgiving-selector\n\t\t\t\t\t// like the `:has()` pseudo-class:\n\t\t\t\t\t// https://drafts.csswg.org/selectors/#relational\n\t\t\t\t\t// `CSS.supports` is still expected to return `false` then:\n\t\t\t\t\t// https://drafts.csswg.org/css-conditional-4/#typedef-supports-selector-fn\n\t\t\t\t\t// https://drafts.csswg.org/css-conditional-4/#dfn-support-selector\n\t\t\t\t\tif ( support.cssSupportsSelector &&\n\n\t\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\t\t!CSS.supports( \"selector(:is(\" + newSelector + \"))\" ) ) {\n\n\t\t\t\t\t\t// Support: IE 11+\n\t\t\t\t\t\t// Throw to get to the same code path as an error directly in qSA.\n\t\t\t\t\t\t// Note: once we only support browser supporting\n\t\t\t\t\t\t// `CSS.supports('selector(...)')`, we can most likely drop\n\t\t\t\t\t\t// the `try-catch`. IE doesn't implement the API.\n\t\t\t\t\t\tthrow new Error();\n\t\t\t\t\t}\n\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch ( qsaError ) {\n\t\t\t\t\tnonnativeSelectorCache( selector, true );\n\t\t\t\t} finally {\n\t\t\t\t\tif ( nid === expando ) {\n\t\t\t\t\t\tcontext.removeAttribute( \"id\" );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {function(string, object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn ( cache[ key + \" \" ] = value );\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created element and returns a boolean result\n */\nfunction assert( fn ) {\n\tvar el = document.createElement( \"fieldset\" );\n\n\ttry {\n\t\treturn !!fn( el );\n\t} catch ( e ) {\n\t\treturn false;\n\t} finally {\n\n\t\t// Remove from its parent by default\n\t\tif ( el.parentNode ) {\n\t\t\tel.parentNode.removeChild( el );\n\t\t}\n\n\t\t// release memory in IE\n\t\tel = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split( \"|\" ),\n\t\ti = arr.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[ i ] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\ta.sourceIndex - b.sourceIndex;\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( ( cur = cur.nextSibling ) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn ( name === \"input\" || name === \"button\" ) && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for :enabled/:disabled\n * @param {Boolean} disabled true for :disabled; false for :enabled\n */\nfunction createDisabledPseudo( disabled ) {\n\n\t// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable\n\treturn function( elem ) {\n\n\t\t// Only certain elements can match :enabled or :disabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled\n\t\tif ( \"form\" in elem ) {\n\n\t\t\t// Check for inherited disabledness on relevant non-disabled elements:\n\t\t\t// * listed form-associated elements in a disabled fieldset\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#category-listed\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled\n\t\t\t// * option elements in a disabled optgroup\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled\n\t\t\t// All such elements have a \"form\" property.\n\t\t\tif ( elem.parentNode && elem.disabled === false ) {\n\n\t\t\t\t// Option elements defer to a parent optgroup if present\n\t\t\t\tif ( \"label\" in elem ) {\n\t\t\t\t\tif ( \"label\" in elem.parentNode ) {\n\t\t\t\t\t\treturn elem.parentNode.disabled === disabled;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn elem.disabled === disabled;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Support: IE 6 - 11\n\t\t\t\t// Use the isDisabled shortcut property to check for disabled fieldset ancestors\n\t\t\t\treturn elem.isDisabled === disabled ||\n\n\t\t\t\t\t// Where there is no isDisabled, check manually\n\t\t\t\t\t/* jshint -W018 */\n\t\t\t\t\telem.isDisabled !== !disabled &&\n\t\t\t\t\tinDisabledFieldset( elem ) === disabled;\n\t\t\t}\n\n\t\t\treturn elem.disabled === disabled;\n\n\t\t// Try to winnow out elements that can't be disabled before trusting the disabled property.\n\t\t// Some victims get caught in our net (label, legend, menu, track), but it shouldn't\n\t\t// even exist on them, let alone have a boolean value.\n\t\t} else if ( \"label\" in elem ) {\n\t\t\treturn elem.disabled === disabled;\n\t\t}\n\n\t\t// Remaining elements are neither :enabled nor :disabled\n\t\treturn false;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction( function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction( function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ ( j = matchIndexes[ i ] ) ] ) {\n\t\t\t\t\tseed[ j ] = !( matches[ j ] = seed[ j ] );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t} );\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== \"undefined\" && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\tvar namespace = elem && elem.namespaceURI,\n\t\tdocElem = elem && ( elem.ownerDocument || elem ).documentElement;\n\n\t// Support: IE <=8\n\t// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes\n\t// https://bugs.jquery.com/ticket/4833\n\treturn !rhtml.test( namespace || docElem && docElem.nodeName || \"HTML\" );\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare, subWindow,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc;\n\n\t// Return early if doc is invalid or already selected\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Update global variables\n\tdocument = doc;\n\tdocElem = document.documentElement;\n\tdocumentIsHTML = !isXML( document );\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t// Accessing iframe documents after unload throws \"permission denied\" errors (jQuery #13936)\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( preferredDoc != document &&\n\t\t( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {\n\n\t\t// Support: IE 11, Edge\n\t\tif ( subWindow.addEventListener ) {\n\t\t\tsubWindow.addEventListener( \"unload\", unloadHandler, false );\n\n\t\t// Support: IE 9 - 10 only\n\t\t} else if ( subWindow.attachEvent ) {\n\t\t\tsubWindow.attachEvent( \"onunload\", unloadHandler );\n\t\t}\n\t}\n\n\t// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,\n\t// Safari 4 - 5 only, Opera <=11.6 - 12.x only\n\t// IE/Edge & older browsers don't support the :scope pseudo-class.\n\t// Support: Safari 6.0 only\n\t// Safari 6.0 supports :scope but it's an alias of :root there.\n\tsupport.scope = assert( function( el ) {\n\t\tdocElem.appendChild( el ).appendChild( document.createElement( \"div\" ) );\n\t\treturn typeof el.querySelectorAll !== \"undefined\" &&\n\t\t\t!el.querySelectorAll( \":scope fieldset div\" ).length;\n\t} );\n\n\t// Support: Chrome 105+, Firefox 104+, Safari 15.4+\n\t// Make sure forgiving mode is not used in `CSS.supports( \"selector(...)\" )`.\n\t//\n\t// `:is()` uses a forgiving selector list as an argument and is widely\n\t// implemented, so it's a good one to test against.\n\tsupport.cssSupportsSelector = assert( function() {\n\t\t/* eslint-disable no-undef */\n\n\t\treturn CSS.supports( \"selector(*)\" ) &&\n\n\t\t\t// Support: Firefox 78-81 only\n\t\t\t// In old Firefox, `:is()` didn't use forgiving parsing. In that case,\n\t\t\t// fail this test as there's no selector to test against that.\n\t\t\t// `CSS.supports` uses unforgiving parsing\n\t\t\tdocument.querySelectorAll( \":is(:jqfake)\" ) &&\n\n\t\t\t// `*` is needed as Safari & newer Chrome implemented something in between\n\t\t\t// for `:has()` - it throws in `qSA` if it only contains an unsupported\n\t\t\t// argument but multiple ones, one of which is supported, are fine.\n\t\t\t// We want to play safe in case `:is()` gets the same treatment.\n\t\t\t!CSS.supports( \"selector(:is(*,:jqfake))\" );\n\n\t\t/* eslint-enable */\n\t} );\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties\n\t// (excepting IE8 booleans)\n\tsupport.attributes = assert( function( el ) {\n\t\tel.className = \"i\";\n\t\treturn !el.getAttribute( \"className\" );\n\t} );\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert( function( el ) {\n\t\tel.appendChild( document.createComment( \"\" ) );\n\t\treturn !el.getElementsByTagName( \"*\" ).length;\n\t} );\n\n\t// Support: IE<9\n\tsupport.getElementsByClassName = rnative.test( document.getElementsByClassName );\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programmatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert( function( el ) {\n\t\tdocElem.appendChild( el ).id = expando;\n\t\treturn !document.getElementsByName || !document.getElementsByName( expando ).length;\n\t} );\n\n\t// ID filter and find\n\tif ( support.getById ) {\n\t\tExpr.filter[ \"ID\" ] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute( \"id\" ) === attrId;\n\t\t\t};\n\t\t};\n\t\tExpr.find[ \"ID\" ] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar elem = context.getElementById( id );\n\t\t\t\treturn elem ? [ elem ] : [];\n\t\t\t}\n\t\t};\n\t} else {\n\t\tExpr.filter[ \"ID\" ] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== \"undefined\" &&\n\t\t\t\t\telem.getAttributeNode( \"id\" );\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\n\t\t// Support: IE 6 - 7 only\n\t\t// getElementById is not reliable as a find shortcut\n\t\tExpr.find[ \"ID\" ] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar node, i, elems,\n\t\t\t\t\telem = context.getElementById( id );\n\n\t\t\t\tif ( elem ) {\n\n\t\t\t\t\t// Verify the id attribute\n\t\t\t\t\tnode = elem.getAttributeNode( \"id\" );\n\t\t\t\t\tif ( node && node.value === id ) {\n\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fall back on getElementsByName\n\t\t\t\t\telems = context.getElementsByName( id );\n\t\t\t\t\ti = 0;\n\t\t\t\t\twhile ( ( elem = elems[ i++ ] ) ) {\n\t\t\t\t\t\tnode = elem.getAttributeNode( \"id\" );\n\t\t\t\t\t\tif ( node && node.value === id ) {\n\t\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn [];\n\t\t\t}\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[ \"TAG\" ] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\n\t\t\t// DocumentFragment nodes don't have gEBTN\n\t\t\t} else if ( support.qsa ) {\n\t\t\t\treturn context.querySelectorAll( tag );\n\t\t\t}\n\t\t} :\n\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\n\t\t\t\t// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( ( elem = results[ i++ ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[ \"CLASS\" ] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== \"undefined\" && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See https://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {\n\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert( function( el ) {\n\n\t\t\tvar input;\n\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// https://bugs.jquery.com/ticket/12359\n\t\t\tdocElem.appendChild( el ).innerHTML = \"<a id='\" + expando + \"'></a>\" +\n\t\t\t\t\"<select id='\" + expando + \"-\\r\\\\' msallowcapture=''>\" +\n\t\t\t\t\"<option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 11-12.16\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\t// The test attribute must be unknown in Opera but \"safe\" for WinRT\n\t\t\t// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section\n\t\t\tif ( el.querySelectorAll( \"[msallowcapture^='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !el.querySelectorAll( \"[selected]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+\n\t\t\tif ( !el.querySelectorAll( \"[id~=\" + expando + \"-]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"~=\" );\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 15 - 18+\n\t\t\t// IE 11/Edge don't find elements on a `[name='']` query in some cases.\n\t\t\t// Adding a temporary attribute to the document before the selection works\n\t\t\t// around the issue.\n\t\t\t// Interestingly, IE 10 & older don't seem to have the issue.\n\t\t\tinput = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"name\", \"\" );\n\t\t\tel.appendChild( input );\n\t\t\tif ( !el.querySelectorAll( \"[name='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*name\" + whitespace + \"*=\" +\n\t\t\t\t\twhitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !el.querySelectorAll( \":checked\" ).length ) {\n\t\t\t\trbuggyQSA.push( \":checked\" );\n\t\t\t}\n\n\t\t\t// Support: Safari 8+, iOS 8+\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t\t// In-page `selector#id sibling-combinator selector` fails\n\t\t\tif ( !el.querySelectorAll( \"a#\" + expando + \"+*\" ).length ) {\n\t\t\t\trbuggyQSA.push( \".#.+[+~]\" );\n\t\t\t}\n\n\t\t\t// Support: Firefox <=3.6 - 5 only\n\t\t\t// Old Firefox doesn't throw on a badly-escaped identifier.\n\t\t\t// el.querySelectorAll( \"\\\\\\f\" );\n\t\t\t// rbuggyQSA.push( \"[\\\\r\\\\n\\\\f]\" );\n\t\t} );\n\n\t\tassert( function( el ) {\n\t\t\tel.innerHTML = \"<a href='' disabled='disabled'></a>\" +\n\t\t\t\t\"<select disabled='disabled'><option/></select>\";\n\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tel.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( el.querySelectorAll( \"[name=d]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( el.querySelectorAll( \":enabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: IE9-11+\n\t\t\t// IE's :disabled selector does not pick up the children of disabled fieldsets\n\t\t\tdocElem.appendChild( el ).disabled = true;\n\t\t\tif ( el.querySelectorAll( \":disabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: Opera 10 - 11 only\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\t// el.querySelectorAll( \"*,:x\" );\n\t\t\t// rbuggyQSA.push( \",.*:\" );\n\t\t} );\n\t}\n\n\tif ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||\n\t\tdocElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector ) ) ) ) {\n\n\t\tassert( function( el ) {\n\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( el, \"*\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\t// matches.call( el, \"[s!='']:x\" );\n\t\t\t// rbuggyMatches.push( \"!=\", pseudos );\n\t\t} );\n\t}\n\n\tif ( !support.cssSupportsSelector ) {\n\n\t\t// Support: Chrome 105+, Safari 15.4+\n\t\t// `:has()` uses a forgiving selector list as an argument so our regular\n\t\t// `try-catch` mechanism fails to catch `:has()` with arguments not supported\n\t\t// natively like `:has(:contains(\"Foo\"))`. Where supported & spec-compliant,\n\t\t// we now use `CSS.supports(\"selector(:is(SELECTOR_TO_BE_TESTED))\")`, but\n\t\t// outside that we mark `:has` as buggy.\n\t\trbuggyQSA.push( \":has\" );\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( \"|\" ) );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( \"|\" ) );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully self-exclusive\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\n\t\t\t// Support: IE <9 only\n\t\t\t// IE doesn't have `contains` on `document` so we need to check for\n\t\t\t// `documentElement` presence.\n\t\t\t// We need to fall back to `a` when `documentElement` is missing\n\t\t\t// as `ownerDocument` of elements within `<template/>` may have\n\t\t\t// a null one - a default behavior of all modern browsers.\n\t\t\tvar adown = a.nodeType === 9 && a.documentElement || a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t) );\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( ( b = b.parentNode ) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t// two documents; shallow comparisons work.\n\t\t// eslint-disable-next-line eqeqeq\n\t\tcompare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif ( a == document || a.ownerDocument == preferredDoc &&\n\t\t\t\tcontains( preferredDoc, a ) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif ( b == document || b.ownerDocument == preferredDoc &&\n\t\t\t\tcontains( preferredDoc, b ) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t/* eslint-disable eqeqeq */\n\t\t\treturn a == document ? -1 :\n\t\t\t\tb == document ? 1 :\n\t\t\t\t/* eslint-enable eqeqeq */\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( ( cur = cur.parentNode ) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( ( cur = cur.parentNode ) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[ i ] === bp[ i ] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[ i ], bp[ i ] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t/* eslint-disable eqeqeq */\n\t\t\tap[ i ] == preferredDoc ? -1 :\n\t\t\tbp[ i ] == preferredDoc ? 1 :\n\t\t\t/* eslint-enable eqeqeq */\n\t\t\t0;\n\t};\n\n\treturn document;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\tsetDocument( elem );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t!nonnativeSelectorCache[ expr + \" \" ] &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\n\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t// fragment in IE 9\n\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\tnonnativeSelectorCache( expr, true );\n\t\t}\n\t}\n\n\treturn Sizzle( expr, document, null, [ elem ] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( ( context.ownerDocument || context ) != document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( ( elem.ownerDocument || elem ) != document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t( val = elem.getAttributeNode( name ) ) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.escape = function( sel ) {\n\treturn ( sel + \"\" ).replace( rcssescape, fcssescape );\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( ( elem = results[ i++ ] ) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( ( node = elem[ i++ ] ) ) {\n\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[ 1 ] = match[ 1 ].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[ 3 ] = ( match[ 3 ] || match[ 4 ] ||\n\t\t\t\tmatch[ 5 ] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[ 2 ] === \"~=\" ) {\n\t\t\t\tmatch[ 3 ] = \" \" + match[ 3 ] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[ 1 ] = match[ 1 ].toLowerCase();\n\n\t\t\tif ( match[ 1 ].slice( 0, 3 ) === \"nth\" ) {\n\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[ 3 ] ) {\n\t\t\t\t\tSizzle.error( match[ 0 ] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[ 4 ] = +( match[ 4 ] ?\n\t\t\t\t\tmatch[ 5 ] + ( match[ 6 ] || 1 ) :\n\t\t\t\t\t2 * ( match[ 3 ] === \"even\" || match[ 3 ] === \"odd\" ) );\n\t\t\t\tmatch[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === \"odd\" );\n\n\t\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[ 3 ] ) {\n\t\t\t\tSizzle.error( match[ 0 ] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[ 6 ] && match[ 2 ];\n\n\t\t\tif ( matchExpr[ \"CHILD\" ].test( match[ 0 ] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[ 3 ] ) {\n\t\t\t\tmatch[ 2 ] = match[ 4 ] || match[ 5 ] || \"\";\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t( excess = tokenize( unquoted, true ) ) &&\n\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t( excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length ) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[ 0 ] = match[ 0 ].slice( 0, excess );\n\t\t\t\tmatch[ 2 ] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() {\n\t\t\t\t\treturn true;\n\t\t\t\t} :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t( pattern = new RegExp( \"(^|\" + whitespace +\n\t\t\t\t\t\")\" + className + \"(\" + whitespace + \"|$)\" ) ) && classCache(\n\t\t\t\t\t\tclassName, function( elem ) {\n\t\t\t\t\t\t\treturn pattern.test(\n\t\t\t\t\t\t\t\ttypeof elem.className === \"string\" && elem.className ||\n\t\t\t\t\t\t\t\ttypeof elem.getAttribute !== \"undefined\" &&\n\t\t\t\t\t\t\t\t\telem.getAttribute( \"class\" ) ||\n\t\t\t\t\t\t\t\t\"\"\n\t\t\t\t\t\t\t);\n\t\t\t\t} );\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\t/* eslint-disable max-len */\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result.replace( rwhitespace, \" \" ) + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t\t/* eslint-enable max-len */\n\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, _argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, _context, xml ) {\n\t\t\t\t\tvar cache, uniqueCache, outerCache, node, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType,\n\t\t\t\t\t\tdiff = false;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( ( node = node[ dir ] ) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ?\n\t\t\t\t\t\t\t\t\t\tnode.nodeName.toLowerCase() === name :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType === 1 ) {\n\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\n\t\t\t\t\t\t\t// ...in a gzip-friendly way\n\t\t\t\t\t\t\tnode = parent;\n\t\t\t\t\t\t\touterCache = node[ expando ] || ( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\tcache = uniqueCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];\n\t\t\t\t\t\t\tdiff = nodeIndex && cache[ 2 ];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( ( node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t( diff = nodeIndex = 0 ) || start.pop() ) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\tuniqueCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t\tif ( useCache ) {\n\n\t\t\t\t\t\t\t\t// ...in a gzip-friendly way\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\touterCache = node[ expando ] || ( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\t\tcache = uniqueCache[ type ] || [];\n\t\t\t\t\t\t\t\tnodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];\n\t\t\t\t\t\t\t\tdiff = nodeIndex;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// xml :nth-child(...)\n\t\t\t\t\t\t\t// or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t\tif ( diff === false ) {\n\n\t\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\t\twhile ( ( node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t\t( diff = nodeIndex = 0 ) || start.pop() ) ) {\n\n\t\t\t\t\t\t\t\t\tif ( ( ofType ?\n\t\t\t\t\t\t\t\t\t\tnode.nodeName.toLowerCase() === name :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType === 1 ) &&\n\t\t\t\t\t\t\t\t\t\t++diff ) {\n\n\t\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t\touterCache = node[ expando ] ||\n\t\t\t\t\t\t\t\t\t\t\t\t( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\t\t\t\t\tuniqueCache[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction( function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf( seed, matched[ i ] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t} ) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction( function( selector ) {\n\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction( function( seed, matches, _context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( ( elem = unmatched[ i ] ) ) {\n\t\t\t\t\t\t\tseed[ i ] = !( matches[ i ] = elem );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} ) :\n\t\t\t\tfunction( elem, _context, xml ) {\n\t\t\t\t\tinput[ 0 ] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\n\t\t\t\t\t// Don't keep the element (issue #299)\n\t\t\t\t\tinput[ 0 ] = null;\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t} ),\n\n\t\t\"has\": markFunction( function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t} ),\n\n\t\t\"contains\": markFunction( function( text ) {\n\t\t\ttext = text.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t} ),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test( lang || \"\" ) ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( ( elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute( \"xml:lang\" ) || elem.getAttribute( \"lang\" ) ) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t} ),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement &&\n\t\t\t\t( !document.hasFocus || document.hasFocus() ) &&\n\t\t\t\t!!( elem.type || elem.href || ~elem.tabIndex );\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": createDisabledPseudo( false ),\n\t\t\"disabled\": createDisabledPseudo( true ),\n\n\t\t\"checked\": function( elem ) {\n\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn ( nodeName === \"input\" && !!elem.checked ) ||\n\t\t\t\t( nodeName === \"option\" && !!elem.selected );\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\t// eslint-disable-next-line no-unused-expressions\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[ \"empty\" ]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE <10 only\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( ( attr = elem.getAttribute( \"type\" ) ) == null ||\n\t\t\t\t\tattr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo( function() {\n\t\t\treturn [ 0 ];\n\t\t} ),\n\n\t\t\"last\": createPositionalPseudo( function( _matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t} ),\n\n\t\t\"eq\": createPositionalPseudo( function( _matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t} ),\n\n\t\t\"even\": createPositionalPseudo( function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"odd\": createPositionalPseudo( function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"lt\": createPositionalPseudo( function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ?\n\t\t\t\targument + length :\n\t\t\t\targument > length ?\n\t\t\t\t\tlength :\n\t\t\t\t\targument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"gt\": createPositionalPseudo( function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} )\n\t}\n};\n\nExpr.pseudos[ \"nth\" ] = Expr.pseudos[ \"eq\" ];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\ntokenize = Sizzle.tokenize = function( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || ( match = rcomma.exec( soFar ) ) ) {\n\t\t\tif ( match ) {\n\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[ 0 ].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( ( tokens = [] ) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( ( match = rcombinators.exec( soFar ) ) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push( {\n\t\t\t\tvalue: matched,\n\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[ 0 ].replace( rtrim, \" \" )\n\t\t\t} );\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||\n\t\t\t\t( match = preFilters[ type ]( match ) ) ) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push( {\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t} );\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n};\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[ i ].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tskip = combinator.next,\n\t\tkey = skip || dir,\n\t\tcheckNonElements = base && key === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, uniqueCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || ( elem[ expando ] = {} );\n\n\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\tuniqueCache = outerCache[ elem.uniqueID ] ||\n\t\t\t\t\t\t\t( outerCache[ elem.uniqueID ] = {} );\n\n\t\t\t\t\t\tif ( skip && skip === elem.nodeName.toLowerCase() ) {\n\t\t\t\t\t\t\telem = elem[ dir ] || elem;\n\t\t\t\t\t\t} else if ( ( oldCache = uniqueCache[ key ] ) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn ( newCache[ 2 ] = oldCache[ 2 ] );\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\tuniqueCache[ key ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[ i ]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[ 0 ];\n}\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[ i ], results );\n\t}\n\treturn results;\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( ( elem = unmatched[ i ] ) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction( function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts(\n\t\t\t\tselector || \"*\",\n\t\t\t\tcontext.nodeType ? [ context ] : context,\n\t\t\t\t[]\n\t\t\t),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( ( elem = temp[ i ] ) ) {\n\t\t\t\t\tmatcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( ( elem = matcherOut[ i ] ) ) {\n\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( ( matcherIn[ i ] = elem ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, ( matcherOut = [] ), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( ( elem = matcherOut[ i ] ) &&\n\t\t\t\t\t\t( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {\n\n\t\t\t\t\t\tseed[ temp ] = !( results[ temp ] = elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t} );\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[ 0 ].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[ \" \" ],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\tvar ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t( checkContext = context ).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\n\t\t\t// Avoid hanging onto element (issue #299)\n\t\t\tcheckContext = null;\n\t\t\treturn ret;\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {\n\t\t\tmatchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[ j ].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\n\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\ttokens\n\t\t\t\t\t\t.slice( 0, i - 1 )\n\t\t\t\t\t\t.concat( { value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" } )\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[ \"TAG\" ]( \"*\", outermost ),\n\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\n\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\toutermostContext = context == document || context || outermost;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\n\t\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\t\tif ( !context && elem.ownerDocument != document ) {\n\t\t\t\t\t\tsetDocument( elem );\n\t\t\t\t\t\txml = !documentIsHTML;\n\t\t\t\t\t}\n\t\t\t\t\twhile ( ( matcher = elementMatchers[ j++ ] ) ) {\n\t\t\t\t\t\tif ( matcher( elem, context || document, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( ( elem = !matcher && elem ) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// `i` is now the count of elements visited above, and adding it to `matchedCount`\n\t\t\t// makes the latter nonnegative.\n\t\t\tmatchedCount += i;\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\t// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`\n\t\t\t// equals `i`), unless we didn't visit _any_ elements in the above loop because we have\n\t\t\t// no element matchers and no seed.\n\t\t\t// Incrementing an initially-string \"0\" `i` allows `i` to remain a string only in that\n\t\t\t// case, which will result in a \"00\" `matchedCount` that differs from `i` but is also\n\t\t\t// numerically zero.\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( ( matcher = setMatchers[ j++ ] ) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !( unmatched[ i ] || setMatched[ i ] ) ) {\n\t\t\t\t\t\t\t\tsetMatched[ i ] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !match ) {\n\t\t\tmatch = tokenize( selector );\n\t\t}\n\t\ti = match.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( match[ i ] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache(\n\t\t\tselector,\n\t\t\tmatcherFromGroupMatchers( elementMatchers, setMatchers )\n\t\t);\n\n\t\t// Save selector and tokenization\n\t\tcached.selector = selector;\n\t}\n\treturn cached;\n};\n\n/**\n * A low-level selection function that works with Sizzle's compiled\n *  selector functions\n * @param {String|Function} selector A selector or a pre-compiled\n *  selector function built with Sizzle.compile\n * @param {Element} context\n * @param {Array} [results]\n * @param {Array} [seed] A set of elements to match against\n */\nselect = Sizzle.select = function( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tcompiled = typeof selector === \"function\" && selector,\n\t\tmatch = !seed && tokenize( ( selector = compiled.selector || selector ) );\n\n\tresults = results || [];\n\n\t// Try to minimize operations if there is only one selector in the list and no seed\n\t// (the latter of which guarantees us context)\n\tif ( match.length === 1 ) {\n\n\t\t// Reduce context if the leading compound selector is an ID\n\t\ttokens = match[ 0 ] = match[ 0 ].slice( 0 );\n\t\tif ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === \"ID\" &&\n\t\t\tcontext.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {\n\n\t\t\tcontext = ( Expr.find[ \"ID\" ]( token.matches[ 0 ]\n\t\t\t\t.replace( runescape, funescape ), context ) || [] )[ 0 ];\n\t\t\tif ( !context ) {\n\t\t\t\treturn results;\n\n\t\t\t// Precompiled matchers will still verify ancestry, so step up a level\n\t\t\t} else if ( compiled ) {\n\t\t\t\tcontext = context.parentNode;\n\t\t\t}\n\n\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t}\n\n\t\t// Fetch a seed set for right-to-left matching\n\t\ti = matchExpr[ \"needsContext\" ].test( selector ) ? 0 : tokens.length;\n\t\twhile ( i-- ) {\n\t\t\ttoken = tokens[ i ];\n\n\t\t\t// Abort if we hit a combinator\n\t\t\tif ( Expr.relative[ ( type = token.type ) ] ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( ( find = Expr.find[ type ] ) ) {\n\n\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\tif ( ( seed = find(\n\t\t\t\t\ttoken.matches[ 0 ].replace( runescape, funescape ),\n\t\t\t\t\trsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||\n\t\t\t\t\t\tcontext\n\t\t\t\t) ) ) {\n\n\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function if one is not provided\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\t( compiled || compile( selector, match ) )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\t!context || rsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n};\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split( \"\" ).sort( sortOrder ).join( \"\" ) === expando;\n\n// Support: Chrome 14-35+\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert( function( el ) {\n\n\t// Should return 1, but returns 4 (following)\n\treturn el.compareDocumentPosition( document.createElement( \"fieldset\" ) ) & 1;\n} );\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert( function( el ) {\n\tel.innerHTML = \"<a href='#'></a>\";\n\treturn el.firstChild.getAttribute( \"href\" ) === \"#\";\n} ) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t} );\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert( function( el ) {\n\tel.innerHTML = \"<input/>\";\n\tel.firstChild.setAttribute( \"value\", \"\" );\n\treturn el.firstChild.getAttribute( \"value\" ) === \"\";\n} ) ) {\n\taddHandle( \"value\", function( elem, _name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t} );\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert( function( el ) {\n\treturn el.getAttribute( \"disabled\" ) == null;\n} ) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t( val = elem.getAttributeNode( name ) ) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\t\tnull;\n\t\t}\n\t} );\n}\n\nreturn Sizzle;\n\n} )( window );\n\n\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\n\n// Deprecated\njQuery.expr[ \":\" ] = jQuery.expr.pseudos;\njQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\njQuery.escapeSelector = Sizzle.escape;\n\n\n\n\nvar dir = function( elem, dir, until ) {\n\tvar matched = [],\n\t\ttruncate = until !== undefined;\n\n\twhile ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {\n\t\tif ( elem.nodeType === 1 ) {\n\t\t\tif ( truncate && jQuery( elem ).is( until ) ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmatched.push( elem );\n\t\t}\n\t}\n\treturn matched;\n};\n\n\nvar siblings = function( n, elem ) {\n\tvar matched = [];\n\n\tfor ( ; n; n = n.nextSibling ) {\n\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\tmatched.push( n );\n\t\t}\n\t}\n\n\treturn matched;\n};\n\n\nvar rneedsContext = jQuery.expr.match.needsContext;\n\n\n\nfunction nodeName( elem, name ) {\n\n\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\n}\nvar rsingleTag = ( /^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i );\n\n\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t} );\n\t}\n\n\t// Single element\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t} );\n\t}\n\n\t// Arraylike of elements (jQuery, arguments, Array)\n\tif ( typeof qualifier !== \"string\" ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( indexOf.call( qualifier, elem ) > -1 ) !== not;\n\t\t} );\n\t}\n\n\t// Filtered directly for both simple and complex selectors\n\treturn jQuery.filter( qualifier, elements, not );\n}\n\njQuery.filter = function( expr, elems, not ) {\n\tvar elem = elems[ 0 ];\n\n\tif ( not ) {\n\t\texpr = \":not(\" + expr + \")\";\n\t}\n\n\tif ( elems.length === 1 && elem.nodeType === 1 ) {\n\t\treturn jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];\n\t}\n\n\treturn jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\treturn elem.nodeType === 1;\n\t} ) );\n};\n\njQuery.fn.extend( {\n\tfind: function( selector ) {\n\t\tvar i, ret,\n\t\t\tlen = this.length,\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter( function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} ) );\n\t\t}\n\n\t\tret = this.pushStack( [] );\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\treturn len > 1 ? jQuery.uniqueSort( ret ) : ret;\n\t},\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow( this, selector || [], false ) );\n\t},\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow( this, selector || [], true ) );\n\t},\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t}\n} );\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)\n\t// Strict HTML recognition (trac-11290: must start with <)\n\t// Shortcut simple #id case for speed\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/,\n\n\tinit = jQuery.fn.init = function( selector, context, root ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Method init() accepts an alternate rootjQuery\n\t\t// so migrate can support jQuery.sub (gh-2101)\n\t\troot = root || rootjQuery;\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector[ 0 ] === \"<\" &&\n\t\t\t\tselector[ selector.length - 1 ] === \">\" &&\n\t\t\t\tselector.length >= 3 ) {\n\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && ( match[ 1 ] || !context ) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[ 1 ] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[ 0 ] : context;\n\n\t\t\t\t\t// Option to run scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[ 1 ],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[ 2 ] );\n\n\t\t\t\t\tif ( elem ) {\n\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis[ 0 ] = elem;\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || root ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis[ 0 ] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( isFunction( selector ) ) {\n\t\t\treturn root.ready !== undefined ?\n\t\t\t\troot.ready( selector ) :\n\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector( jQuery );\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\nrootjQuery = jQuery( document );\n\n\nvar rparentsprev = /^(?:parents|prev(?:Until|All))/,\n\n\t// Methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.fn.extend( {\n\thas: function( target ) {\n\t\tvar targets = jQuery( target, this ),\n\t\t\tl = targets.length;\n\n\t\treturn this.filter( function() {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[ i ] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tmatched = [],\n\t\t\ttargets = typeof selectors !== \"string\" && jQuery( selectors );\n\n\t\t// Positional selectors never match, since there's no _selection_ context\n\t\tif ( !rneedsContext.test( selectors ) ) {\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tfor ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {\n\n\t\t\t\t\t// Always skip document fragments\n\t\t\t\t\tif ( cur.nodeType < 11 && ( targets ?\n\t\t\t\t\t\ttargets.index( cur ) > -1 :\n\n\t\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\t\tjQuery.find.matchesSelector( cur, selectors ) ) ) {\n\n\t\t\t\t\t\tmatched.push( cur );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );\n\t},\n\n\t// Determine the position of an element within the set\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// Index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn indexOf.call( jQuery( elem ), this[ 0 ] );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call( this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ] : elem\n\t\t);\n\t},\n\n\tadd: function( selector, context ) {\n\t\treturn this.pushStack(\n\t\t\tjQuery.uniqueSort(\n\t\t\t\tjQuery.merge( this.get(), jQuery( selector, context ) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter( selector )\n\t\t);\n\t}\n} );\n\nfunction sibling( cur, dir ) {\n\twhile ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}\n\treturn cur;\n}\n\njQuery.each( {\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, _i, until ) {\n\t\treturn dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, _i, until ) {\n\t\treturn dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, _i, until ) {\n\t\treturn dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn siblings( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn siblings( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\tif ( elem.contentDocument != null &&\n\n\t\t\t// Support: IE 11+\n\t\t\t// <object> elements with no `data` attribute has an object\n\t\t\t// `contentDocument` with a `null` prototype.\n\t\t\tgetProto( elem.contentDocument ) ) {\n\n\t\t\treturn elem.contentDocument;\n\t\t}\n\n\t\t// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only\n\t\t// Treat the template element as a regular one in browsers that\n\t\t// don't support it.\n\t\tif ( nodeName( elem, \"template\" ) ) {\n\t\t\telem = elem.content || elem;\n\t\t}\n\n\t\treturn jQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar matched = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tmatched = jQuery.filter( selector, matched );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tjQuery.uniqueSort( matched );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched );\n\t};\n} );\nvar rnothtmlwhite = ( /[^\\x20\\t\\r\\n\\f]+/g );\n\n\n\n// Convert String-formatted options into Object-formatted ones\nfunction createOptions( options ) {\n\tvar object = {};\n\tjQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t} );\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\tcreateOptions( options ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Flag to know if list is currently firing\n\t\tfiring,\n\n\t\t// Last fire value for non-forgettable lists\n\t\tmemory,\n\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\n\t\t// Flag to prevent firing\n\t\tlocked,\n\n\t\t// Actual callback list\n\t\tlist = [],\n\n\t\t// Queue of execution data for repeatable lists\n\t\tqueue = [],\n\n\t\t// Index of currently firing callback (modified by add/remove as needed)\n\t\tfiringIndex = -1,\n\n\t\t// Fire callbacks\n\t\tfire = function() {\n\n\t\t\t// Enforce single-firing\n\t\t\tlocked = locked || options.once;\n\n\t\t\t// Execute callbacks for all pending executions,\n\t\t\t// respecting firingIndex overrides and runtime changes\n\t\t\tfired = firing = true;\n\t\t\tfor ( ; queue.length; firingIndex = -1 ) {\n\t\t\t\tmemory = queue.shift();\n\t\t\t\twhile ( ++firingIndex < list.length ) {\n\n\t\t\t\t\t// Run callback and check for early termination\n\t\t\t\t\tif ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&\n\t\t\t\t\t\toptions.stopOnFalse ) {\n\n\t\t\t\t\t\t// Jump to end and forget the data so .add doesn't re-fire\n\t\t\t\t\t\tfiringIndex = list.length;\n\t\t\t\t\t\tmemory = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Forget the data if we're done with it\n\t\t\tif ( !options.memory ) {\n\t\t\t\tmemory = false;\n\t\t\t}\n\n\t\t\tfiring = false;\n\n\t\t\t// Clean up if we're done firing for good\n\t\t\tif ( locked ) {\n\n\t\t\t\t// Keep an empty list if we have data for future add calls\n\t\t\t\tif ( memory ) {\n\t\t\t\t\tlist = [];\n\n\t\t\t\t// Otherwise, this object is spent\n\t\t\t\t} else {\n\t\t\t\t\tlist = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Actual Callbacks object\n\t\tself = {\n\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\n\t\t\t\t\t// If we have memory from a past run, we should fire after adding\n\t\t\t\t\tif ( memory && !firing ) {\n\t\t\t\t\t\tfiringIndex = list.length - 1;\n\t\t\t\t\t\tqueue.push( memory );\n\t\t\t\t\t}\n\n\t\t\t\t\t( function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tif ( isFunction( arg ) ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && toType( arg ) !== \"string\" ) {\n\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\t\t\t\t\t} )( arguments );\n\n\t\t\t\t\tif ( memory && !firing ) {\n\t\t\t\t\t\tfire();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\tvar index;\n\t\t\t\t\twhile ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\tlist.splice( index, 1 );\n\n\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ?\n\t\t\t\t\tjQuery.inArray( fn, list ) > -1 :\n\t\t\t\t\tlist.length > 0;\n\t\t\t},\n\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Disable .fire and .add\n\t\t\t// Abort any current/pending executions\n\t\t\t// Clear all callbacks and values\n\t\t\tdisable: function() {\n\t\t\t\tlocked = queue = [];\n\t\t\t\tlist = memory = \"\";\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\n\t\t\t// Disable .fire\n\t\t\t// Also disable .add unless we have memory (since it would have no effect)\n\t\t\t// Abort any pending executions\n\t\t\tlock: function() {\n\t\t\t\tlocked = queue = [];\n\t\t\t\tif ( !memory && !firing ) {\n\t\t\t\t\tlist = memory = \"\";\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\tlocked: function() {\n\t\t\t\treturn !!locked;\n\t\t\t},\n\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( !locked ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tqueue.push( args );\n\t\t\t\t\tif ( !firing ) {\n\t\t\t\t\t\tfire();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\nfunction Identity( v ) {\n\treturn v;\n}\nfunction Thrower( ex ) {\n\tthrow ex;\n}\n\nfunction adoptValue( value, resolve, reject, noValue ) {\n\tvar method;\n\n\ttry {\n\n\t\t// Check for promise aspect first to privilege synchronous behavior\n\t\tif ( value && isFunction( ( method = value.promise ) ) ) {\n\t\t\tmethod.call( value ).done( resolve ).fail( reject );\n\n\t\t// Other thenables\n\t\t} else if ( value && isFunction( ( method = value.then ) ) ) {\n\t\t\tmethod.call( value, resolve, reject );\n\n\t\t// Other non-thenables\n\t\t} else {\n\n\t\t\t// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:\n\t\t\t// * false: [ value ].slice( 0 ) => resolve( value )\n\t\t\t// * true: [ value ].slice( 1 ) => resolve()\n\t\t\tresolve.apply( undefined, [ value ].slice( noValue ) );\n\t\t}\n\n\t// For Promises/A+, convert exceptions into rejections\n\t// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in\n\t// Deferred#then to conditionally suppress rejection.\n\t} catch ( value ) {\n\n\t\t// Support: Android 4.0 only\n\t\t// Strict mode functions invoked without .call/.apply get global-object context\n\t\treject.apply( undefined, [ value ] );\n\t}\n}\n\njQuery.extend( {\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\n\t\t\t\t// action, add listener, callbacks,\n\t\t\t\t// ... .then handlers, argument index, [final state]\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks( \"memory\" ),\n\t\t\t\t\tjQuery.Callbacks( \"memory\" ), 2 ],\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks( \"once memory\" ),\n\t\t\t\t\tjQuery.Callbacks( \"once memory\" ), 0, \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks( \"once memory\" ),\n\t\t\t\t\tjQuery.Callbacks( \"once memory\" ), 1, \"rejected\" ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\t\"catch\": function( fn ) {\n\t\t\t\t\treturn promise.then( null, fn );\n\t\t\t\t},\n\n\t\t\t\t// Keep pipe for back-compat\n\t\t\t\tpipe: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\n\t\t\t\t\treturn jQuery.Deferred( function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( _i, tuple ) {\n\n\t\t\t\t\t\t\t// Map tuples (progress, done, fail) to arguments (done, fail, progress)\n\t\t\t\t\t\t\tvar fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];\n\n\t\t\t\t\t\t\t// deferred.progress(function() { bind to newDefer or newDefer.notify })\n\t\t\t\t\t\t\t// deferred.done(function() { bind to newDefer or newDefer.resolve })\n\t\t\t\t\t\t\t// deferred.fail(function() { bind to newDefer or newDefer.reject })\n\t\t\t\t\t\t\tdeferred[ tuple[ 1 ] ]( function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify )\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + \"With\" ](\n\t\t\t\t\t\t\t\t\t\tthis,\n\t\t\t\t\t\t\t\t\t\tfn ? [ returned ] : arguments\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t} );\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t} ).promise();\n\t\t\t\t},\n\t\t\t\tthen: function( onFulfilled, onRejected, onProgress ) {\n\t\t\t\t\tvar maxDepth = 0;\n\t\t\t\t\tfunction resolve( depth, deferred, handler, special ) {\n\t\t\t\t\t\treturn function() {\n\t\t\t\t\t\t\tvar that = this,\n\t\t\t\t\t\t\t\targs = arguments,\n\t\t\t\t\t\t\t\tmightThrow = function() {\n\t\t\t\t\t\t\t\t\tvar returned, then;\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.3\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-59\n\t\t\t\t\t\t\t\t\t// Ignore double-resolution attempts\n\t\t\t\t\t\t\t\t\tif ( depth < maxDepth ) {\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturned = handler.apply( that, args );\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.1\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-48\n\t\t\t\t\t\t\t\t\tif ( returned === deferred.promise() ) {\n\t\t\t\t\t\t\t\t\t\tthrow new TypeError( \"Thenable self-resolution\" );\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ sections 2.3.3.1, 3.5\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-54\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-75\n\t\t\t\t\t\t\t\t\t// Retrieve `then` only once\n\t\t\t\t\t\t\t\t\tthen = returned &&\n\n\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.4\n\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-64\n\t\t\t\t\t\t\t\t\t\t// Only check objects and functions for thenability\n\t\t\t\t\t\t\t\t\t\t( typeof returned === \"object\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof returned === \"function\" ) &&\n\t\t\t\t\t\t\t\t\t\treturned.then;\n\n\t\t\t\t\t\t\t\t\t// Handle a returned thenable\n\t\t\t\t\t\t\t\t\tif ( isFunction( then ) ) {\n\n\t\t\t\t\t\t\t\t\t\t// Special processors (notify) just wait for resolution\n\t\t\t\t\t\t\t\t\t\tif ( special ) {\n\t\t\t\t\t\t\t\t\t\t\tthen.call(\n\t\t\t\t\t\t\t\t\t\t\t\treturned,\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Identity, special ),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Thrower, special )\n\t\t\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\t\t// Normal processors (resolve) also hook into progress\n\t\t\t\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t\t\t\t// ...and disregard older resolution values\n\t\t\t\t\t\t\t\t\t\t\tmaxDepth++;\n\n\t\t\t\t\t\t\t\t\t\t\tthen.call(\n\t\t\t\t\t\t\t\t\t\t\t\treturned,\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Identity, special ),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Thrower, special ),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Identity,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdeferred.notifyWith )\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Handle all other returned values\n\t\t\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t\t\t// Only substitute handlers pass on context\n\t\t\t\t\t\t\t\t\t\t// and multiple values (non-spec behavior)\n\t\t\t\t\t\t\t\t\t\tif ( handler !== Identity ) {\n\t\t\t\t\t\t\t\t\t\t\tthat = undefined;\n\t\t\t\t\t\t\t\t\t\t\targs = [ returned ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t// Process the value(s)\n\t\t\t\t\t\t\t\t\t\t// Default process is resolve\n\t\t\t\t\t\t\t\t\t\t( special || deferred.resolveWith )( that, args );\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\n\t\t\t\t\t\t\t\t// Only normal processors (resolve) catch and reject exceptions\n\t\t\t\t\t\t\t\tprocess = special ?\n\t\t\t\t\t\t\t\t\tmightThrow :\n\t\t\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tmightThrow();\n\t\t\t\t\t\t\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t\t\t\t\t\t\tif ( jQuery.Deferred.exceptionHook ) {\n\t\t\t\t\t\t\t\t\t\t\t\tjQuery.Deferred.exceptionHook( e,\n\t\t\t\t\t\t\t\t\t\t\t\t\tprocess.stackTrace );\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.4.1\n\t\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-61\n\t\t\t\t\t\t\t\t\t\t\t// Ignore post-resolution exceptions\n\t\t\t\t\t\t\t\t\t\t\tif ( depth + 1 >= maxDepth ) {\n\n\t\t\t\t\t\t\t\t\t\t\t\t// Only substitute handlers pass on context\n\t\t\t\t\t\t\t\t\t\t\t\t// and multiple values (non-spec behavior)\n\t\t\t\t\t\t\t\t\t\t\t\tif ( handler !== Thrower ) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthat = undefined;\n\t\t\t\t\t\t\t\t\t\t\t\t\targs = [ e ];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\tdeferred.rejectWith( that, args );\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.1\n\t\t\t\t\t\t\t// https://promisesaplus.com/#point-57\n\t\t\t\t\t\t\t// Re-resolve promises immediately to dodge false rejection from\n\t\t\t\t\t\t\t// subsequent errors\n\t\t\t\t\t\t\tif ( depth ) {\n\t\t\t\t\t\t\t\tprocess();\n\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t// Call an optional hook to record the stack, in case of exception\n\t\t\t\t\t\t\t\t// since it's otherwise lost when execution goes async\n\t\t\t\t\t\t\t\tif ( jQuery.Deferred.getStackHook ) {\n\t\t\t\t\t\t\t\t\tprocess.stackTrace = jQuery.Deferred.getStackHook();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.setTimeout( process );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn jQuery.Deferred( function( newDefer ) {\n\n\t\t\t\t\t\t// progress_handlers.add( ... )\n\t\t\t\t\t\ttuples[ 0 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction( onProgress ) ?\n\t\t\t\t\t\t\t\t\tonProgress :\n\t\t\t\t\t\t\t\t\tIdentity,\n\t\t\t\t\t\t\t\tnewDefer.notifyWith\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// fulfilled_handlers.add( ... )\n\t\t\t\t\t\ttuples[ 1 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction( onFulfilled ) ?\n\t\t\t\t\t\t\t\t\tonFulfilled :\n\t\t\t\t\t\t\t\t\tIdentity\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// rejected_handlers.add( ... )\n\t\t\t\t\t\ttuples[ 2 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction( onRejected ) ?\n\t\t\t\t\t\t\t\t\tonRejected :\n\t\t\t\t\t\t\t\t\tThrower\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} ).promise();\n\t\t\t\t},\n\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 5 ];\n\n\t\t\t// promise.progress = list.add\n\t\t\t// promise.done = list.add\n\t\t\t// promise.fail = list.add\n\t\t\tpromise[ tuple[ 1 ] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(\n\t\t\t\t\tfunction() {\n\n\t\t\t\t\t\t// state = \"resolved\" (i.e., fulfilled)\n\t\t\t\t\t\t// state = \"rejected\"\n\t\t\t\t\t\tstate = stateString;\n\t\t\t\t\t},\n\n\t\t\t\t\t// rejected_callbacks.disable\n\t\t\t\t\t// fulfilled_callbacks.disable\n\t\t\t\t\ttuples[ 3 - i ][ 2 ].disable,\n\n\t\t\t\t\t// rejected_handlers.disable\n\t\t\t\t\t// fulfilled_handlers.disable\n\t\t\t\t\ttuples[ 3 - i ][ 3 ].disable,\n\n\t\t\t\t\t// progress_callbacks.lock\n\t\t\t\t\ttuples[ 0 ][ 2 ].lock,\n\n\t\t\t\t\t// progress_handlers.lock\n\t\t\t\t\ttuples[ 0 ][ 3 ].lock\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// progress_handlers.fire\n\t\t\t// fulfilled_handlers.fire\n\t\t\t// rejected_handlers.fire\n\t\t\tlist.add( tuple[ 3 ].fire );\n\n\t\t\t// deferred.notify = function() { deferred.notifyWith(...) }\n\t\t\t// deferred.resolve = function() { deferred.resolveWith(...) }\n\t\t\t// deferred.reject = function() { deferred.rejectWith(...) }\n\t\t\tdeferred[ tuple[ 0 ] ] = function() {\n\t\t\t\tdeferred[ tuple[ 0 ] + \"With\" ]( this === deferred ? undefined : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\n\t\t\t// deferred.notifyWith = list.fireWith\n\t\t\t// deferred.resolveWith = list.fireWith\n\t\t\t// deferred.rejectWith = list.fireWith\n\t\t\tdeferred[ tuple[ 0 ] + \"With\" ] = list.fireWith;\n\t\t} );\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( singleValue ) {\n\t\tvar\n\n\t\t\t// count of uncompleted subordinates\n\t\t\tremaining = arguments.length,\n\n\t\t\t// count of unprocessed arguments\n\t\t\ti = remaining,\n\n\t\t\t// subordinate fulfillment data\n\t\t\tresolveContexts = Array( i ),\n\t\t\tresolveValues = slice.call( arguments ),\n\n\t\t\t// the primary Deferred\n\t\t\tprimary = jQuery.Deferred(),\n\n\t\t\t// subordinate callback factory\n\t\t\tupdateFunc = function( i ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tresolveContexts[ i ] = this;\n\t\t\t\t\tresolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;\n\t\t\t\t\tif ( !( --remaining ) ) {\n\t\t\t\t\t\tprimary.resolveWith( resolveContexts, resolveValues );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t};\n\n\t\t// Single- and empty arguments are adopted like Promise.resolve\n\t\tif ( remaining <= 1 ) {\n\t\t\tadoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,\n\t\t\t\t!remaining );\n\n\t\t\t// Use .then() to unwrap secondary thenables (cf. gh-3000)\n\t\t\tif ( primary.state() === \"pending\" ||\n\t\t\t\tisFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {\n\n\t\t\t\treturn primary.then();\n\t\t\t}\n\t\t}\n\n\t\t// Multiple arguments are aggregated like Promise.all array elements\n\t\twhile ( i-- ) {\n\t\t\tadoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );\n\t\t}\n\n\t\treturn primary.promise();\n\t}\n} );\n\n\n// These usually indicate a programmer mistake during development,\n// warn about them ASAP rather than swallowing them by default.\nvar rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;\n\njQuery.Deferred.exceptionHook = function( error, stack ) {\n\n\t// Support: IE 8 - 9 only\n\t// Console exists when dev tools are open, which can happen at any time\n\tif ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {\n\t\twindow.console.warn( \"jQuery.Deferred exception: \" + error.message, error.stack, stack );\n\t}\n};\n\n\n\n\njQuery.readyException = function( error ) {\n\twindow.setTimeout( function() {\n\t\tthrow error;\n\t} );\n};\n\n\n\n\n// The deferred used on DOM ready\nvar readyList = jQuery.Deferred();\n\njQuery.fn.ready = function( fn ) {\n\n\treadyList\n\t\t.then( fn )\n\n\t\t// Wrap jQuery.readyException in a function so that the lookup\n\t\t// happens at the time of error handling instead of callback\n\t\t// registration.\n\t\t.catch( function( error ) {\n\t\t\tjQuery.readyException( error );\n\t\t} );\n\n\treturn this;\n};\n\njQuery.extend( {\n\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See trac-6781\n\treadyWait: 1,\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\t}\n} );\n\njQuery.ready.then = readyList.then;\n\n// The ready event handler and self cleanup method\nfunction completed() {\n\tdocument.removeEventListener( \"DOMContentLoaded\", completed );\n\twindow.removeEventListener( \"load\", completed );\n\tjQuery.ready();\n}\n\n// Catch cases where $(document).ready() is called\n// after the browser event has already occurred.\n// Support: IE <=9 - 10 only\n// Older IE sometimes signals \"interactive\" too soon\nif ( document.readyState === \"complete\" ||\n\t( document.readyState !== \"loading\" && !document.documentElement.doScroll ) ) {\n\n\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\twindow.setTimeout( jQuery.ready );\n\n} else {\n\n\t// Use the handy event callback\n\tdocument.addEventListener( \"DOMContentLoaded\", completed );\n\n\t// A fallback to window.onload, that will always work\n\twindow.addEventListener( \"load\", completed );\n}\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\nvar access = function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\tvar i = 0,\n\t\tlen = elems.length,\n\t\tbulk = key == null;\n\n\t// Sets many values\n\tif ( toType( key ) === \"object\" ) {\n\t\tchainable = true;\n\t\tfor ( i in key ) {\n\t\t\taccess( elems, fn, i, key[ i ], true, emptyGet, raw );\n\t\t}\n\n\t// Sets one value\n\t} else if ( value !== undefined ) {\n\t\tchainable = true;\n\n\t\tif ( !isFunction( value ) ) {\n\t\t\traw = true;\n\t\t}\n\n\t\tif ( bulk ) {\n\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif ( raw ) {\n\t\t\t\tfn.call( elems, value );\n\t\t\t\tfn = null;\n\n\t\t\t// ...except when executing function values\n\t\t\t} else {\n\t\t\t\tbulk = fn;\n\t\t\t\tfn = function( elem, _key, value ) {\n\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif ( fn ) {\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\tfn(\n\t\t\t\t\telems[ i ], key, raw ?\n\t\t\t\t\t\tvalue :\n\t\t\t\t\t\tvalue.call( elems[ i ], i, fn( elems[ i ], key ) )\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( chainable ) {\n\t\treturn elems;\n\t}\n\n\t// Gets\n\tif ( bulk ) {\n\t\treturn fn.call( elems );\n\t}\n\n\treturn len ? fn( elems[ 0 ], key ) : emptyGet;\n};\n\n\n// Matches dashed string for camelizing\nvar rmsPrefix = /^-ms-/,\n\trdashAlpha = /-([a-z])/g;\n\n// Used by camelCase as callback to replace()\nfunction fcamelCase( _all, letter ) {\n\treturn letter.toUpperCase();\n}\n\n// Convert dashed to camelCase; used by the css and data modules\n// Support: IE <=9 - 11, Edge 12 - 15\n// Microsoft forgot to hump their vendor prefix (trac-9572)\nfunction camelCase( string ) {\n\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n}\nvar acceptData = function( owner ) {\n\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\treturn owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n};\n\n\n\n\nfunction Data() {\n\tthis.expando = jQuery.expando + Data.uid++;\n}\n\nData.uid = 1;\n\nData.prototype = {\n\n\tcache: function( owner ) {\n\n\t\t// Check if the owner object already has a cache\n\t\tvar value = owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif ( !value ) {\n\t\t\tvalue = {};\n\n\t\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t\t// but we should not, see trac-8335.\n\t\t\t// Always return an empty object.\n\t\t\tif ( acceptData( owner ) ) {\n\n\t\t\t\t// If it is a node unlikely to be stringify-ed or looped over\n\t\t\t\t// use plain assignment\n\t\t\t\tif ( owner.nodeType ) {\n\t\t\t\t\towner[ this.expando ] = value;\n\n\t\t\t\t// Otherwise secure it in a non-enumerable property\n\t\t\t\t// configurable must be true to allow the property to be\n\t\t\t\t// deleted when data is removed\n\t\t\t\t} else {\n\t\t\t\t\tObject.defineProperty( owner, this.expando, {\n\t\t\t\t\t\tvalue: value,\n\t\t\t\t\t\tconfigurable: true\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn value;\n\t},\n\tset: function( owner, data, value ) {\n\t\tvar prop,\n\t\t\tcache = this.cache( owner );\n\n\t\t// Handle: [ owner, key, value ] args\n\t\t// Always use camelCase key (gh-2257)\n\t\tif ( typeof data === \"string\" ) {\n\t\t\tcache[ camelCase( data ) ] = value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t} else {\n\n\t\t\t// Copy the properties one-by-one to the cache object\n\t\t\tfor ( prop in data ) {\n\t\t\t\tcache[ camelCase( prop ) ] = data[ prop ];\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function( owner, key ) {\n\t\treturn key === undefined ?\n\t\t\tthis.cache( owner ) :\n\n\t\t\t// Always use camelCase key (gh-2257)\n\t\t\towner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];\n\t},\n\taccess: function( owner, key, value ) {\n\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the \"read\" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif ( key === undefined ||\n\t\t\t\t( ( key && typeof key === \"string\" ) && value === undefined ) ) {\n\n\t\t\treturn this.get( owner, key );\n\t\t}\n\n\t\t// When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set( owner, key, value );\n\n\t\t// Since the \"set\" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value !== undefined ? value : key;\n\t},\n\tremove: function( owner, key ) {\n\t\tvar i,\n\t\t\tcache = owner[ this.expando ];\n\n\t\tif ( cache === undefined ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( key !== undefined ) {\n\n\t\t\t// Support array or space separated string of keys\n\t\t\tif ( Array.isArray( key ) ) {\n\n\t\t\t\t// If key is an array of keys...\n\t\t\t\t// We always set camelCase keys, so remove that.\n\t\t\t\tkey = key.map( camelCase );\n\t\t\t} else {\n\t\t\t\tkey = camelCase( key );\n\n\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\tkey = key in cache ?\n\t\t\t\t\t[ key ] :\n\t\t\t\t\t( key.match( rnothtmlwhite ) || [] );\n\t\t\t}\n\n\t\t\ti = key.length;\n\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete cache[ key[ i ] ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if there's no more data\n\t\tif ( key === undefined || jQuery.isEmptyObject( cache ) ) {\n\n\t\t\t// Support: Chrome <=35 - 45\n\t\t\t// Webkit & Blink performance suffers when deleting properties\n\t\t\t// from DOM nodes, so set to undefined instead\n\t\t\t// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)\n\t\t\tif ( owner.nodeType ) {\n\t\t\t\towner[ this.expando ] = undefined;\n\t\t\t} else {\n\t\t\t\tdelete owner[ this.expando ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function( owner ) {\n\t\tvar cache = owner[ this.expando ];\n\t\treturn cache !== undefined && !jQuery.isEmptyObject( cache );\n\t}\n};\nvar dataPriv = new Data();\n\nvar dataUser = new Data();\n\n\n\n//\tImplementation Summary\n//\n//\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n//\t2. Improve the module's maintainability by reducing the storage\n//\t\tpaths to a single mechanism.\n//\t3. Use the same single mechanism to support \"private\" and \"user\" data.\n//\t4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n//\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n//\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n\nvar rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash = /[A-Z]/g;\n\nfunction getData( data ) {\n\tif ( data === \"true\" ) {\n\t\treturn true;\n\t}\n\n\tif ( data === \"false\" ) {\n\t\treturn false;\n\t}\n\n\tif ( data === \"null\" ) {\n\t\treturn null;\n\t}\n\n\t// Only convert to a number if it doesn't change the string\n\tif ( data === +data + \"\" ) {\n\t\treturn +data;\n\t}\n\n\tif ( rbrace.test( data ) ) {\n\t\treturn JSON.parse( data );\n\t}\n\n\treturn data;\n}\n\nfunction dataAttr( elem, key, data ) {\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\t\tname = \"data-\" + key.replace( rmultiDash, \"-$&\" ).toLowerCase();\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = getData( data );\n\t\t\t} catch ( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tdataUser.set( elem, key, data );\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend( {\n\thasData: function( elem ) {\n\t\treturn dataUser.hasData( elem ) || dataPriv.hasData( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn dataUser.access( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\tdataUser.remove( elem, name );\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to dataPriv methods, these can be deprecated.\n\t_data: function( elem, name, data ) {\n\t\treturn dataPriv.access( elem, name, data );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\tdataPriv.remove( elem, name );\n\t}\n} );\n\njQuery.fn.extend( {\n\tdata: function( key, value ) {\n\t\tvar i, name, data,\n\t\t\telem = this[ 0 ],\n\t\t\tattrs = elem && elem.attributes;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = dataUser.get( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !dataPriv.get( elem, \"hasDataAttrs\" ) ) {\n\t\t\t\t\ti = attrs.length;\n\t\t\t\t\twhile ( i-- ) {\n\n\t\t\t\t\t\t// Support: IE 11 only\n\t\t\t\t\t\t// The attrs elements can be null (trac-14894)\n\t\t\t\t\t\tif ( attrs[ i ] ) {\n\t\t\t\t\t\t\tname = attrs[ i ].name;\n\t\t\t\t\t\t\tif ( name.indexOf( \"data-\" ) === 0 ) {\n\t\t\t\t\t\t\t\tname = camelCase( name.slice( 5 ) );\n\t\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdataPriv.set( elem, \"hasDataAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each( function() {\n\t\t\t\tdataUser.set( this, key );\n\t\t\t} );\n\t\t}\n\n\t\treturn access( this, function( value ) {\n\t\t\tvar data;\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem = this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif ( elem && value === undefined ) {\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// The key will always be camelCased in Data\n\t\t\t\tdata = dataUser.get( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to \"discover\" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata = dataAttr( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn't exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each( function() {\n\n\t\t\t\t// We always store the camelCased key\n\t\t\t\tdataUser.set( this, key, value );\n\t\t\t} );\n\t\t}, null, value, arguments.length > 1, null, true );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each( function() {\n\t\t\tdataUser.remove( this, key );\n\t\t} );\n\t}\n} );\n\n\njQuery.extend( {\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = dataPriv.get( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || Array.isArray( data ) ) {\n\t\t\t\t\tqueue = dataPriv.access( elem, type, jQuery.makeArray( data ) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// Clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// Not public - generate a queueHooks object, or return the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn dataPriv.get( elem, key ) || dataPriv.access( elem, key, {\n\t\t\tempty: jQuery.Callbacks( \"once memory\" ).add( function() {\n\t\t\t\tdataPriv.remove( elem, [ type + \"queue\", key ] );\n\t\t\t} )\n\t\t} );\n\t}\n} );\n\njQuery.fn.extend( {\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[ 0 ], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each( function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// Ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[ 0 ] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t} );\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each( function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t} );\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile ( i-- ) {\n\t\t\ttmp = dataPriv.get( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n} );\nvar pnum = ( /[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/ ).source;\n\nvar rcssNum = new RegExp( \"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\" );\n\n\nvar cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\nvar documentElement = document.documentElement;\n\n\n\n\tvar isAttached = function( elem ) {\n\t\t\treturn jQuery.contains( elem.ownerDocument, elem );\n\t\t},\n\t\tcomposed = { composed: true };\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only\n\t// Check attachment across shadow DOM boundaries when possible (gh-3504)\n\t// Support: iOS 10.0-10.2 only\n\t// Early iOS 10 versions support `attachShadow` but not `getRootNode`,\n\t// leading to errors. We need to check for `getRootNode`.\n\tif ( documentElement.getRootNode ) {\n\t\tisAttached = function( elem ) {\n\t\t\treturn jQuery.contains( elem.ownerDocument, elem ) ||\n\t\t\t\telem.getRootNode( composed ) === elem.ownerDocument;\n\t\t};\n\t}\nvar isHiddenWithinTree = function( elem, el ) {\n\n\t\t// isHiddenWithinTree might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem = el || elem;\n\n\t\t// Inline style trumps all\n\t\treturn elem.style.display === \"none\" ||\n\t\t\telem.style.display === \"\" &&\n\n\t\t\t// Otherwise, check computed style\n\t\t\t// Support: Firefox <=43 - 45\n\t\t\t// Disconnected elements can have computed display: none, so first confirm that elem is\n\t\t\t// in the document.\n\t\t\tisAttached( elem ) &&\n\n\t\t\tjQuery.css( elem, \"display\" ) === \"none\";\n\t};\n\n\n\nfunction adjustCSS( elem, prop, valueParts, tween ) {\n\tvar adjusted, scale,\n\t\tmaxIterations = 20,\n\t\tcurrentValue = tween ?\n\t\t\tfunction() {\n\t\t\t\treturn tween.cur();\n\t\t\t} :\n\t\t\tfunction() {\n\t\t\t\treturn jQuery.css( elem, prop, \"\" );\n\t\t\t},\n\t\tinitial = currentValue(),\n\t\tunit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t// Starting value computation is required for potential unit mismatches\n\t\tinitialInUnit = elem.nodeType &&\n\t\t\t( jQuery.cssNumber[ prop ] || unit !== \"px\" && +initial ) &&\n\t\t\trcssNum.exec( jQuery.css( elem, prop ) );\n\n\tif ( initialInUnit && initialInUnit[ 3 ] !== unit ) {\n\n\t\t// Support: Firefox <=54\n\t\t// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)\n\t\tinitial = initial / 2;\n\n\t\t// Trust units reported by jQuery.css\n\t\tunit = unit || initialInUnit[ 3 ];\n\n\t\t// Iteratively approximate from a nonzero starting point\n\t\tinitialInUnit = +initial || 1;\n\n\t\twhile ( maxIterations-- ) {\n\n\t\t\t// Evaluate and update our best guess (doubling guesses that zero out).\n\t\t\t// Finish if the scale equals or crosses 1 (making the old*new product non-positive).\n\t\t\tjQuery.style( elem, prop, initialInUnit + unit );\n\t\t\tif ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {\n\t\t\t\tmaxIterations = 0;\n\t\t\t}\n\t\t\tinitialInUnit = initialInUnit / scale;\n\n\t\t}\n\n\t\tinitialInUnit = initialInUnit * 2;\n\t\tjQuery.style( elem, prop, initialInUnit + unit );\n\n\t\t// Make sure we update the tween properties later on\n\t\tvalueParts = valueParts || [];\n\t}\n\n\tif ( valueParts ) {\n\t\tinitialInUnit = +initialInUnit || +initial || 0;\n\n\t\t// Apply relative offset (+=/-=) if specified\n\t\tadjusted = valueParts[ 1 ] ?\n\t\t\tinitialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :\n\t\t\t+valueParts[ 2 ];\n\t\tif ( tween ) {\n\t\t\ttween.unit = unit;\n\t\t\ttween.start = initialInUnit;\n\t\t\ttween.end = adjusted;\n\t\t}\n\t}\n\treturn adjusted;\n}\n\n\nvar defaultDisplayMap = {};\n\nfunction getDefaultDisplay( elem ) {\n\tvar temp,\n\t\tdoc = elem.ownerDocument,\n\t\tnodeName = elem.nodeName,\n\t\tdisplay = defaultDisplayMap[ nodeName ];\n\n\tif ( display ) {\n\t\treturn display;\n\t}\n\n\ttemp = doc.body.appendChild( doc.createElement( nodeName ) );\n\tdisplay = jQuery.css( temp, \"display\" );\n\n\ttemp.parentNode.removeChild( temp );\n\n\tif ( display === \"none\" ) {\n\t\tdisplay = \"block\";\n\t}\n\tdefaultDisplayMap[ nodeName ] = display;\n\n\treturn display;\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\t// Determine new display value for elements that need to change\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\n\t\t\t// Since we force visibility upon cascade-hidden elements, an immediate (and slow)\n\t\t\t// check is required in this first loop unless we have a nonempty display value (either\n\t\t\t// inline or about-to-be-restored)\n\t\t\tif ( display === \"none\" ) {\n\t\t\t\tvalues[ index ] = dataPriv.get( elem, \"display\" ) || null;\n\t\t\t\tif ( !values[ index ] ) {\n\t\t\t\t\telem.style.display = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( elem.style.display === \"\" && isHiddenWithinTree( elem ) ) {\n\t\t\t\tvalues[ index ] = getDefaultDisplay( elem );\n\t\t\t}\n\t\t} else {\n\t\t\tif ( display !== \"none\" ) {\n\t\t\t\tvalues[ index ] = \"none\";\n\n\t\t\t\t// Remember what we're overwriting\n\t\t\t\tdataPriv.set( elem, \"display\", display );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of the elements in a second loop to avoid constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\tif ( values[ index ] != null ) {\n\t\t\telements[ index ].style.display = values[ index ];\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.fn.extend( {\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tif ( isHiddenWithinTree( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t} );\n\t}\n} );\nvar rcheckableType = ( /^(?:checkbox|radio)$/i );\n\nvar rtagName = ( /<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i );\n\nvar rscriptType = ( /^$|^module$|\\/(?:java|ecma)script/i );\n\n\n\n( function() {\n\tvar fragment = document.createDocumentFragment(),\n\t\tdiv = fragment.appendChild( document.createElement( \"div\" ) ),\n\t\tinput = document.createElement( \"input\" );\n\n\t// Support: Android 4.0 - 4.3 only\n\t// Check state lost if the name is set (trac-11217)\n\t// Support: Windows Web Apps (WWA)\n\t// `name` and `type` must use .setAttribute for WWA (trac-14901)\n\tinput.setAttribute( \"type\", \"radio\" );\n\tinput.setAttribute( \"checked\", \"checked\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tdiv.appendChild( input );\n\n\t// Support: Android <=4.1 only\n\t// Older WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE <=11 only\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\tdiv.innerHTML = \"<textarea>x</textarea>\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n\n\t// Support: IE <=9 only\n\t// IE <=9 replaces <option> tags with their contents when inserted outside of\n\t// the select element.\n\tdiv.innerHTML = \"<option></option>\";\n\tsupport.option = !!div.lastChild;\n} )();\n\n\n// We have to close these tags to support XHTML (trac-13200)\nvar wrapMap = {\n\n\t// XHTML parsers do not magically insert elements in the\n\t// same way that tag soup parsers do. So we cannot shorten\n\t// this by omitting <tbody> or other required elements.\n\tthead: [ 1, \"<table>\", \"</table>\" ],\n\tcol: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t_default: [ 0, \"\", \"\" ]\n};\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// Support: IE <=9 only\nif ( !support.option ) {\n\twrapMap.optgroup = wrapMap.option = [ 1, \"<select multiple='multiple'>\", \"</select>\" ];\n}\n\n\nfunction getAll( context, tag ) {\n\n\t// Support: IE <=9 - 11 only\n\t// Use typeof to avoid zero-argument method invocation on host objects (trac-15151)\n\tvar ret;\n\n\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\tret = context.getElementsByTagName( tag || \"*\" );\n\n\t} else if ( typeof context.querySelectorAll !== \"undefined\" ) {\n\t\tret = context.querySelectorAll( tag || \"*\" );\n\n\t} else {\n\t\tret = [];\n\t}\n\n\tif ( tag === undefined || tag && nodeName( context, tag ) ) {\n\t\treturn jQuery.merge( [ context ], ret );\n\t}\n\n\treturn ret;\n}\n\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar i = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\tdataPriv.set(\n\t\t\telems[ i ],\n\t\t\t\"globalEval\",\n\t\t\t!refElements || dataPriv.get( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\n\nvar rhtml = /<|&#?\\w+;/;\n\nfunction buildFragment( elems, context, scripts, selection, ignored ) {\n\tvar elem, tmp, tag, wrap, attached, j,\n\t\tfragment = context.createDocumentFragment(),\n\t\tnodes = [],\n\t\ti = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\telem = elems[ i ];\n\n\t\tif ( elem || elem === 0 ) {\n\n\t\t\t// Add nodes directly\n\t\t\tif ( toType( elem ) === \"object\" ) {\n\n\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t// Convert non-html into a text node\n\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t// Convert html into DOM nodes\n\t\t\t} else {\n\t\t\t\ttmp = tmp || fragment.appendChild( context.createElement( \"div\" ) );\n\n\t\t\t\t// Deserialize a standard representation\n\t\t\t\ttag = ( rtagName.exec( elem ) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\ttmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];\n\n\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\tj = wrap[ 0 ];\n\t\t\t\twhile ( j-- ) {\n\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t}\n\n\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t// Remember the top-level container\n\t\t\t\ttmp = fragment.firstChild;\n\n\t\t\t\t// Ensure the created nodes are orphaned (trac-12392)\n\t\t\t\ttmp.textContent = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remove wrapper from fragment\n\tfragment.textContent = \"\";\n\n\ti = 0;\n\twhile ( ( elem = nodes[ i++ ] ) ) {\n\n\t\t// Skip elements already in the context collection (trac-4087)\n\t\tif ( selection && jQuery.inArray( elem, selection ) > -1 ) {\n\t\t\tif ( ignored ) {\n\t\t\t\tignored.push( elem );\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tattached = isAttached( elem );\n\n\t\t// Append to fragment\n\t\ttmp = getAll( fragment.appendChild( elem ), \"script\" );\n\n\t\t// Preserve script evaluation history\n\t\tif ( attached ) {\n\t\t\tsetGlobalEval( tmp );\n\t\t}\n\n\t\t// Capture executables\n\t\tif ( scripts ) {\n\t\t\tj = 0;\n\t\t\twhile ( ( elem = tmp[ j++ ] ) ) {\n\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\tscripts.push( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fragment;\n}\n\n\nvar rtypenamespace = /^([^.]*)(?:\\.(.+)|)/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\n// Support: IE <=9 - 11+\n// focus() and blur() are asynchronous, except when they are no-op.\n// So expect focus to be synchronous when the element is already active,\n// and blur to be synchronous when the element is not already active.\n// (focus and blur are always synchronous in other supported browsers,\n// this just defines when we can count on it).\nfunction expectSync( elem, type ) {\n\treturn ( elem === safeActiveElement() ) === ( type === \"focus\" );\n}\n\n// Support: IE <=9 only\n// Accessing document.activeElement can throw unexpectedly\n// https://bugs.jquery.com/ticket/13393\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\nfunction on( elem, types, selector, data, fn, one ) {\n\tvar origFn, type;\n\n\t// Types can be a map of types/handlers\n\tif ( typeof types === \"object\" ) {\n\n\t\t// ( types-Object, selector, data )\n\t\tif ( typeof selector !== \"string\" ) {\n\n\t\t\t// ( types-Object, data )\n\t\t\tdata = data || selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tfor ( type in types ) {\n\t\t\ton( elem, type, selector, data, types[ type ], one );\n\t\t}\n\t\treturn elem;\n\t}\n\n\tif ( data == null && fn == null ) {\n\n\t\t// ( types, fn )\n\t\tfn = selector;\n\t\tdata = selector = undefined;\n\t} else if ( fn == null ) {\n\t\tif ( typeof selector === \"string\" ) {\n\n\t\t\t// ( types, selector, fn )\n\t\t\tfn = data;\n\t\t\tdata = undefined;\n\t\t} else {\n\n\t\t\t// ( types, data, fn )\n\t\t\tfn = data;\n\t\t\tdata = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t}\n\tif ( fn === false ) {\n\t\tfn = returnFalse;\n\t} else if ( !fn ) {\n\t\treturn elem;\n\t}\n\n\tif ( one === 1 ) {\n\t\torigFn = fn;\n\t\tfn = function( event ) {\n\n\t\t\t// Can use an empty set, since event contains the info\n\t\t\tjQuery().off( event );\n\t\t\treturn origFn.apply( this, arguments );\n\t\t};\n\n\t\t// Use same guid so caller can remove using origFn\n\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t}\n\treturn elem.each( function() {\n\t\tjQuery.event.add( this, types, fn, data, selector );\n\t} );\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = dataPriv.get( elem );\n\n\t\t// Only attach events to objects that accept data\n\t\tif ( !acceptData( elem ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Ensure that invalid selectors throw exceptions at attach time\n\t\t// Evaluate against documentElement in case elem is a non-element node (e.g., document)\n\t\tif ( selector ) {\n\t\t\tjQuery.find.matchesSelector( documentElement, selector );\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !( events = elemData.events ) ) {\n\t\t\tevents = elemData.events = Object.create( null );\n\t\t}\n\t\tif ( !( eventHandle = elemData.handle ) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== \"undefined\" && jQuery.event.triggered !== e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply( elem, arguments ) : undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnothtmlwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[ t ] ) || [];\n\t\t\ttype = origType = tmp[ 1 ];\n\t\t\tnamespaces = ( tmp[ 2 ] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend( {\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join( \".\" )\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !( handlers = events[ type ] ) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif ( !special.setup ||\n\t\t\t\t\tspecial.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = dataPriv.hasData( elem ) && dataPriv.get( elem );\n\n\t\tif ( !elemData || !( events = elemData.events ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnothtmlwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[ t ] ) || [];\n\t\t\ttype = origType = tmp[ 1 ];\n\t\t\tnamespaces = ( tmp[ 2 ] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[ 2 ] &&\n\t\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join( \"\\\\.(?:.*\\\\.|)\" ) + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector ||\n\t\t\t\t\t\tselector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown ||\n\t\t\t\t\tspecial.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove data and the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdataPriv.remove( elem, \"handle events\" );\n\t\t}\n\t},\n\n\tdispatch: function( nativeEvent ) {\n\n\t\tvar i, j, ret, matched, handleObj, handlerQueue,\n\t\t\targs = new Array( arguments.length ),\n\n\t\t\t// Make a writable jQuery.Event from the native event object\n\t\t\tevent = jQuery.event.fix( nativeEvent ),\n\n\t\t\thandlers = (\n\t\t\t\tdataPriv.get( this, \"events\" ) || Object.create( null )\n\t\t\t)[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[ 0 ] = event;\n\n\t\tfor ( i = 1; i < arguments.length; i++ ) {\n\t\t\targs[ i ] = arguments[ i ];\n\t\t}\n\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( ( handleObj = matched.handlers[ j++ ] ) &&\n\t\t\t\t!event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// If the event is namespaced, then each handler is only invoked if it is\n\t\t\t\t// specially universal or its namespaces are a superset of the event's.\n\t\t\t\tif ( !event.rnamespace || handleObj.namespace === false ||\n\t\t\t\t\tevent.rnamespace.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||\n\t\t\t\t\t\thandleObj.handler ).apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( ( event.result = ret ) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, handleObj, sel, matchedHandlers, matchedSelectors,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\tif ( delegateCount &&\n\n\t\t\t// Support: IE <=9\n\t\t\t// Black-hole SVG <use> instance trees (trac-13180)\n\t\t\tcur.nodeType &&\n\n\t\t\t// Support: Firefox <=42\n\t\t\t// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)\n\t\t\t// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click\n\t\t\t// Support: IE 11 only\n\t\t\t// ...but not arrow key \"clicks\" of radio inputs, which can have `button` -1 (gh-2343)\n\t\t\t!( event.type === \"click\" && event.button >= 1 ) ) {\n\n\t\t\tfor ( ; cur !== this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't check non-elements (trac-13208)\n\t\t\t\t// Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)\n\t\t\t\tif ( cur.nodeType === 1 && !( event.type === \"click\" && cur.disabled === true ) ) {\n\t\t\t\t\tmatchedHandlers = [];\n\t\t\t\t\tmatchedSelectors = {};\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (trac-13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matchedSelectors[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatchedSelectors[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) > -1 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matchedSelectors[ sel ] ) {\n\t\t\t\t\t\t\tmatchedHandlers.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matchedHandlers.length ) {\n\t\t\t\t\t\thandlerQueue.push( { elem: cur, handlers: matchedHandlers } );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tcur = this;\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\taddProp: function( name, hook ) {\n\t\tObject.defineProperty( jQuery.Event.prototype, name, {\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\n\t\t\tget: isFunction( hook ) ?\n\t\t\t\tfunction() {\n\t\t\t\t\tif ( this.originalEvent ) {\n\t\t\t\t\t\treturn hook( this.originalEvent );\n\t\t\t\t\t}\n\t\t\t\t} :\n\t\t\t\tfunction() {\n\t\t\t\t\tif ( this.originalEvent ) {\n\t\t\t\t\t\treturn this.originalEvent[ name ];\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\tset: function( value ) {\n\t\t\t\tObject.defineProperty( this, name, {\n\t\t\t\t\tenumerable: true,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\twritable: true,\n\t\t\t\t\tvalue: value\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\t},\n\n\tfix: function( originalEvent ) {\n\t\treturn originalEvent[ jQuery.expando ] ?\n\t\t\toriginalEvent :\n\t\t\tnew jQuery.Event( originalEvent );\n\t},\n\n\tspecial: {\n\t\tload: {\n\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tclick: {\n\n\t\t\t// Utilize native event to ensure correct state for checkable inputs\n\t\t\tsetup: function( data ) {\n\n\t\t\t\t// For mutual compressibility with _default, replace `this` access with a local var.\n\t\t\t\t// `|| data` is dead code meant only to preserve the variable through minification.\n\t\t\t\tvar el = this || data;\n\n\t\t\t\t// Claim the first handler\n\t\t\t\tif ( rcheckableType.test( el.type ) &&\n\t\t\t\t\tel.click && nodeName( el, \"input\" ) ) {\n\n\t\t\t\t\t// dataPriv.set( el, \"click\", ... )\n\t\t\t\t\tleverageNative( el, \"click\", returnTrue );\n\t\t\t\t}\n\n\t\t\t\t// Return false to allow normal processing in the caller\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\ttrigger: function( data ) {\n\n\t\t\t\t// For mutual compressibility with _default, replace `this` access with a local var.\n\t\t\t\t// `|| data` is dead code meant only to preserve the variable through minification.\n\t\t\t\tvar el = this || data;\n\n\t\t\t\t// Force setup before triggering a click\n\t\t\t\tif ( rcheckableType.test( el.type ) &&\n\t\t\t\t\tel.click && nodeName( el, \"input\" ) ) {\n\n\t\t\t\t\tleverageNative( el, \"click\" );\n\t\t\t\t}\n\n\t\t\t\t// Return non-false to allow normal event-path propagation\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, suppress native .click() on links\n\t\t\t// Also prevent it if we're currently inside a leveraged native-event stack\n\t\t\t_default: function( event ) {\n\t\t\t\tvar target = event.target;\n\t\t\t\treturn rcheckableType.test( target.type ) &&\n\t\t\t\t\ttarget.click && nodeName( target, \"input\" ) &&\n\t\t\t\t\tdataPriv.get( target, \"click\" ) ||\n\t\t\t\t\tnodeName( target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn't alert if the returnValue field is not set.\n\t\t\t\tif ( event.result !== undefined && event.originalEvent ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Ensure the presence of an event listener that handles manually-triggered\n// synthetic events by interrupting progress until reinvoked in response to\n// *native* events that it fires directly, ensuring that state changes have\n// already occurred before other listeners are invoked.\nfunction leverageNative( el, type, expectSync ) {\n\n\t// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add\n\tif ( !expectSync ) {\n\t\tif ( dataPriv.get( el, type ) === undefined ) {\n\t\t\tjQuery.event.add( el, type, returnTrue );\n\t\t}\n\t\treturn;\n\t}\n\n\t// Register the controller as a special universal handler for all event namespaces\n\tdataPriv.set( el, type, false );\n\tjQuery.event.add( el, type, {\n\t\tnamespace: false,\n\t\thandler: function( event ) {\n\t\t\tvar notAsync, result,\n\t\t\t\tsaved = dataPriv.get( this, type );\n\n\t\t\tif ( ( event.isTrigger & 1 ) && this[ type ] ) {\n\n\t\t\t\t// Interrupt processing of the outer synthetic .trigger()ed event\n\t\t\t\t// Saved data should be false in such cases, but might be a leftover capture object\n\t\t\t\t// from an async native handler (gh-4350)\n\t\t\t\tif ( !saved.length ) {\n\n\t\t\t\t\t// Store arguments for use when handling the inner native event\n\t\t\t\t\t// There will always be at least one argument (an event object), so this array\n\t\t\t\t\t// will not be confused with a leftover capture object.\n\t\t\t\t\tsaved = slice.call( arguments );\n\t\t\t\t\tdataPriv.set( this, type, saved );\n\n\t\t\t\t\t// Trigger the native event and capture its result\n\t\t\t\t\t// Support: IE <=9 - 11+\n\t\t\t\t\t// focus() and blur() are asynchronous\n\t\t\t\t\tnotAsync = expectSync( this, type );\n\t\t\t\t\tthis[ type ]();\n\t\t\t\t\tresult = dataPriv.get( this, type );\n\t\t\t\t\tif ( saved !== result || notAsync ) {\n\t\t\t\t\t\tdataPriv.set( this, type, false );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = {};\n\t\t\t\t\t}\n\t\t\t\t\tif ( saved !== result ) {\n\n\t\t\t\t\t\t// Cancel the outer synthetic event\n\t\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\t\t// Support: Chrome 86+\n\t\t\t\t\t\t// In Chrome, if an element having a focusout handler is blurred by\n\t\t\t\t\t\t// clicking outside of it, it invokes the handler synchronously. If\n\t\t\t\t\t\t// that handler calls `.remove()` on the element, the data is cleared,\n\t\t\t\t\t\t// leaving `result` undefined. We need to guard against this.\n\t\t\t\t\t\treturn result && result.value;\n\t\t\t\t\t}\n\n\t\t\t\t// If this is an inner synthetic event for an event with a bubbling surrogate\n\t\t\t\t// (focus or blur), assume that the surrogate already propagated from triggering the\n\t\t\t\t// native event and prevent that from happening again here.\n\t\t\t\t// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the\n\t\t\t\t// bubbling surrogate propagates *after* the non-bubbling base), but that seems\n\t\t\t\t// less bad than duplication.\n\t\t\t\t} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}\n\n\t\t\t// If this is a native event triggered above, everything is now in order\n\t\t\t// Fire an inner synthetic event with the original arguments\n\t\t\t} else if ( saved.length ) {\n\n\t\t\t\t// ...and capture the result\n\t\t\t\tdataPriv.set( this, type, {\n\t\t\t\t\tvalue: jQuery.event.trigger(\n\n\t\t\t\t\t\t// Support: IE <=9 - 11+\n\t\t\t\t\t\t// Extend with the prototype to reset the above stopImmediatePropagation()\n\t\t\t\t\t\tjQuery.extend( saved[ 0 ], jQuery.Event.prototype ),\n\t\t\t\t\t\tsaved.slice( 1 ),\n\t\t\t\t\t\tthis\n\t\t\t\t\t)\n\t\t\t\t} );\n\n\t\t\t\t// Abort handling of the native event\n\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t}\n\t\t}\n\t} );\n}\n\njQuery.removeEvent = function( elem, type, handle ) {\n\n\t// This \"if\" is needed for plain objects\n\tif ( elem.removeEventListener ) {\n\t\telem.removeEventListener( type, handle );\n\t}\n};\n\njQuery.Event = function( src, props ) {\n\n\t// Allow instantiation without the 'new' keyword\n\tif ( !( this instanceof jQuery.Event ) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = src.defaultPrevented ||\n\t\t\t\tsrc.defaultPrevented === undefined &&\n\n\t\t\t\t// Support: Android <=2.3 only\n\t\t\t\tsrc.returnValue === false ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t\t// Create target properties\n\t\t// Support: Safari <=6 - 7 only\n\t\t// Target should not be a text node (trac-504, trac-13143)\n\t\tthis.target = ( src.target && src.target.nodeType === 3 ) ?\n\t\t\tsrc.target.parentNode :\n\t\t\tsrc.target;\n\n\t\tthis.currentTarget = src.currentTarget;\n\t\tthis.relatedTarget = src.relatedTarget;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || Date.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tconstructor: jQuery.Event,\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\tisSimulated: false,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tif ( e && !this.isSimulated ) {\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tif ( e && !this.isSimulated ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\n\t\tif ( e && !this.isSimulated ) {\n\t\t\te.stopImmediatePropagation();\n\t\t}\n\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Includes all common event props including KeyEvent and MouseEvent specific props\njQuery.each( {\n\taltKey: true,\n\tbubbles: true,\n\tcancelable: true,\n\tchangedTouches: true,\n\tctrlKey: true,\n\tdetail: true,\n\teventPhase: true,\n\tmetaKey: true,\n\tpageX: true,\n\tpageY: true,\n\tshiftKey: true,\n\tview: true,\n\t\"char\": true,\n\tcode: true,\n\tcharCode: true,\n\tkey: true,\n\tkeyCode: true,\n\tbutton: true,\n\tbuttons: true,\n\tclientX: true,\n\tclientY: true,\n\toffsetX: true,\n\toffsetY: true,\n\tpointerId: true,\n\tpointerType: true,\n\tscreenX: true,\n\tscreenY: true,\n\ttargetTouches: true,\n\ttoElement: true,\n\ttouches: true,\n\twhich: true\n}, jQuery.event.addProp );\n\njQuery.each( { focus: \"focusin\", blur: \"focusout\" }, function( type, delegateType ) {\n\tjQuery.event.special[ type ] = {\n\n\t\t// Utilize native event if possible so blur/focus sequence is correct\n\t\tsetup: function() {\n\n\t\t\t// Claim the first handler\n\t\t\t// dataPriv.set( this, \"focus\", ... )\n\t\t\t// dataPriv.set( this, \"blur\", ... )\n\t\t\tleverageNative( this, type, expectSync );\n\n\t\t\t// Return false to allow normal processing in the caller\n\t\t\treturn false;\n\t\t},\n\t\ttrigger: function() {\n\n\t\t\t// Force setup before trigger\n\t\t\tleverageNative( this, type );\n\n\t\t\t// Return non-false to allow normal event-path propagation\n\t\t\treturn true;\n\t\t},\n\n\t\t// Suppress native focus or blur if we're currently inside\n\t\t// a leveraged native-event stack\n\t\t_default: function( event ) {\n\t\t\treturn dataPriv.get( event.target, type );\n\t\t},\n\n\t\tdelegateType: delegateType\n\t};\n} );\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// so that event delegation works in jQuery.\n// Do the same for pointerenter/pointerleave and pointerover/pointerout\n//\n// Support: Safari 7 only\n// Safari sends mouseenter too often; see:\n// https://bugs.chromium.org/p/chromium/issues/detail?id=470258\n// for the description of the bug (it existed in older Chrome versions as well).\njQuery.each( {\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\",\n\tpointerenter: \"pointerover\",\n\tpointerleave: \"pointerout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mouseenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n} );\n\njQuery.fn.extend( {\n\n\ton: function( types, selector, data, fn ) {\n\t\treturn on( this, types, selector, data, fn );\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn on( this, types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ?\n\t\t\t\t\thandleObj.origType + \".\" + handleObj.namespace :\n\t\t\t\t\thandleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t} );\n\t}\n} );\n\n\nvar\n\n\t// Support: IE <=10 - 11, Edge 12 - 13 only\n\t// In IE/Edge using regex groups here causes severe slowdowns.\n\t// See https://connect.microsoft.com/IE/feedback/details/1736512/\n\trnoInnerhtml = /<script|<style|<link/i,\n\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\n\trcleanScript = /^\\s*<!\\[CDATA\\[|\\]\\]>\\s*$/g;\n\n// Prefer a tbody over its parent table for containing new rows\nfunction manipulationTarget( elem, content ) {\n\tif ( nodeName( elem, \"table\" ) &&\n\t\tnodeName( content.nodeType !== 11 ? content : content.firstChild, \"tr\" ) ) {\n\n\t\treturn jQuery( elem ).children( \"tbody\" )[ 0 ] || elem;\n\t}\n\n\treturn elem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = ( elem.getAttribute( \"type\" ) !== null ) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tif ( ( elem.type || \"\" ).slice( 0, 5 ) === \"true/\" ) {\n\t\telem.type = elem.type.slice( 5 );\n\t} else {\n\t\telem.removeAttribute( \"type\" );\n\t}\n\n\treturn elem;\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\tvar i, l, type, pdataOld, udataOld, udataCur, events;\n\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif ( dataPriv.hasData( src ) ) {\n\t\tpdataOld = dataPriv.get( src );\n\t\tevents = pdataOld.events;\n\n\t\tif ( events ) {\n\t\t\tdataPriv.remove( dest, \"handle events\" );\n\n\t\t\tfor ( type in events ) {\n\t\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif ( dataUser.hasData( src ) ) {\n\t\tudataOld = dataUser.access( src );\n\t\tudataCur = jQuery.extend( {}, udataOld );\n\n\t\tdataUser.set( dest, udataCur );\n\t}\n}\n\n// Fix IE bugs, see support tests\nfunction fixInput( src, dest ) {\n\tvar nodeName = dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\tdest.checked = src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\nfunction domManip( collection, args, callback, ignored ) {\n\n\t// Flatten any nested arrays\n\targs = flat( args );\n\n\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\ti = 0,\n\t\tl = collection.length,\n\t\tiNoClone = l - 1,\n\t\tvalue = args[ 0 ],\n\t\tvalueIsFunction = isFunction( value );\n\n\t// We can't cloneNode fragments that contain checked, in WebKit\n\tif ( valueIsFunction ||\n\t\t\t( l > 1 && typeof value === \"string\" &&\n\t\t\t\t!support.checkClone && rchecked.test( value ) ) ) {\n\t\treturn collection.each( function( index ) {\n\t\t\tvar self = collection.eq( index );\n\t\t\tif ( valueIsFunction ) {\n\t\t\t\targs[ 0 ] = value.call( this, index, self.html() );\n\t\t\t}\n\t\t\tdomManip( self, args, callback, ignored );\n\t\t} );\n\t}\n\n\tif ( l ) {\n\t\tfragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );\n\t\tfirst = fragment.firstChild;\n\n\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\tfragment = first;\n\t\t}\n\n\t\t// Require either new content or an interest in ignored elements to invoke the callback\n\t\tif ( first || ignored ) {\n\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\thasScripts = scripts.length;\n\n\t\t\t// Use the original fragment for the last item\n\t\t\t// instead of the first because it can end up\n\t\t\t// being emptied incorrectly in certain situations (trac-8070).\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tnode = fragment;\n\n\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\tif ( hasScripts ) {\n\n\t\t\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcallback.call( collection[ i ], node, i );\n\t\t\t}\n\n\t\t\tif ( hasScripts ) {\n\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t// Reenable scripts\n\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t!dataPriv.access( node, \"globalEval\" ) &&\n\t\t\t\t\t\tjQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\tif ( node.src && ( node.type || \"\" ).toLowerCase()  !== \"module\" ) {\n\n\t\t\t\t\t\t\t// Optional AJAX dependency, but won't run scripts if not present\n\t\t\t\t\t\t\tif ( jQuery._evalUrl && !node.noModule ) {\n\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src, {\n\t\t\t\t\t\t\t\t\tnonce: node.nonce || node.getAttribute( \"nonce\" )\n\t\t\t\t\t\t\t\t}, doc );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Unwrap a CDATA section containing script contents. This shouldn't be\n\t\t\t\t\t\t\t// needed as in XML documents they're already not visible when\n\t\t\t\t\t\t\t// inspecting element contents and in HTML documents they have no\n\t\t\t\t\t\t\t// meaning but we're preserving that logic for backwards compatibility.\n\t\t\t\t\t\t\t// This will be removed completely in 4.0. See gh-4904.\n\t\t\t\t\t\t\tDOMEval( node.textContent.replace( rcleanScript, \"\" ), node, doc );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn collection;\n}\n\nfunction remove( elem, selector, keepData ) {\n\tvar node,\n\t\tnodes = selector ? jQuery.filter( selector, elem ) : elem,\n\t\ti = 0;\n\n\tfor ( ; ( node = nodes[ i ] ) != null; i++ ) {\n\t\tif ( !keepData && node.nodeType === 1 ) {\n\t\t\tjQuery.cleanData( getAll( node ) );\n\t\t}\n\n\t\tif ( node.parentNode ) {\n\t\t\tif ( keepData && isAttached( node ) ) {\n\t\t\t\tsetGlobalEval( getAll( node, \"script\" ) );\n\t\t\t}\n\t\t\tnode.parentNode.removeChild( node );\n\t\t}\n\t}\n\n\treturn elem;\n}\n\njQuery.extend( {\n\thtmlPrefilter: function( html ) {\n\t\treturn html;\n\t},\n\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone = elem.cloneNode( true ),\n\t\t\tinPage = isAttached( elem );\n\n\t\t// Fix IE cloning issues\n\t\tif ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&\n\t\t\t\t!jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\tfixInput( srcElements[ i ], destElements[ i ] );\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[ i ], destElements[ i ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tcleanData: function( elems ) {\n\t\tvar data, elem, type,\n\t\t\tspecial = jQuery.event.special,\n\t\t\ti = 0;\n\n\t\tfor ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {\n\t\t\tif ( acceptData( elem ) ) {\n\t\t\t\tif ( ( data = elem[ dataPriv.expando ] ) ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: Chrome <=35 - 45+\n\t\t\t\t\t// Assign undefined instead of using delete, see Data#remove\n\t\t\t\t\telem[ dataPriv.expando ] = undefined;\n\t\t\t\t}\n\t\t\t\tif ( elem[ dataUser.expando ] ) {\n\n\t\t\t\t\t// Support: Chrome <=35 - 45+\n\t\t\t\t\t// Assign undefined instead of using delete, see Data#remove\n\t\t\t\t\telem[ dataUser.expando ] = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n} );\n\njQuery.fn.extend( {\n\tdetach: function( selector ) {\n\t\treturn remove( this, selector, true );\n\t},\n\n\tremove: function( selector ) {\n\t\treturn remove( this, selector );\n\t},\n\n\ttext: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().each( function() {\n\t\t\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\t\t\tthis.textContent = value;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t} );\n\t},\n\n\tprepend: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t} );\n\t},\n\n\tbefore: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t} );\n\t},\n\n\tafter: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t} );\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; ( elem = this[ i ] ) != null; i++ ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map( function() {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t} );\n\t},\n\n\thtml: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\tvar elem = this[ 0 ] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined && elem.nodeType === 1 ) {\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ] ) {\n\n\t\t\t\tvalue = jQuery.htmlPrefilter( value );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\t\telem = this[ i ] || {};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch ( e ) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar ignored = [];\n\n\t\t// Make the changes, replacing each non-ignored context element with the new content\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tvar parent = this.parentNode;\n\n\t\t\tif ( jQuery.inArray( this, ignored ) < 0 ) {\n\t\t\t\tjQuery.cleanData( getAll( this ) );\n\t\t\t\tif ( parent ) {\n\t\t\t\t\tparent.replaceChild( elem, this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Force callback invocation\n\t\t}, ignored );\n\t}\n} );\n\njQuery.each( {\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1,\n\t\t\ti = 0;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone( true );\n\t\t\tjQuery( insert[ i ] )[ original ]( elems );\n\n\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t// .get() because push.apply(_, arraylike) throws on ancient WebKit\n\t\t\tpush.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n} );\nvar rnumnonpx = new RegExp( \"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\" );\n\nvar rcustomProp = /^--/;\n\n\nvar getStyles = function( elem ) {\n\n\t\t// Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)\n\t\t// IE throws on elements created in popups\n\t\t// FF meanwhile throws on frame elements through \"defaultView.getComputedStyle\"\n\t\tvar view = elem.ownerDocument.defaultView;\n\n\t\tif ( !view || !view.opener ) {\n\t\t\tview = window;\n\t\t}\n\n\t\treturn view.getComputedStyle( elem );\n\t};\n\nvar swap = function( elem, options, callback ) {\n\tvar ret, name,\n\t\told = {};\n\n\t// Remember the old values, and insert the new ones\n\tfor ( name in options ) {\n\t\told[ name ] = elem.style[ name ];\n\t\telem.style[ name ] = options[ name ];\n\t}\n\n\tret = callback.call( elem );\n\n\t// Revert the old values\n\tfor ( name in options ) {\n\t\telem.style[ name ] = old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar rboxStyle = new RegExp( cssExpand.join( \"|\" ), \"i\" );\n\nvar whitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\";\n\n\nvar rtrimCSS = new RegExp(\n\t\"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\",\n\t\"g\"\n);\n\n\n\n\n( function() {\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they're executed at the same time to save the second computation.\n\tfunction computeStyleTests() {\n\n\t\t// This is a singleton, we need to execute it only once\n\t\tif ( !div ) {\n\t\t\treturn;\n\t\t}\n\n\t\tcontainer.style.cssText = \"position:absolute;left:-11111px;width:60px;\" +\n\t\t\t\"margin-top:1px;padding:0;border:0\";\n\t\tdiv.style.cssText =\n\t\t\t\"position:relative;display:block;box-sizing:border-box;overflow:scroll;\" +\n\t\t\t\"margin:auto;border:1px;padding:1px;\" +\n\t\t\t\"width:60%;top:1%\";\n\t\tdocumentElement.appendChild( container ).appendChild( div );\n\n\t\tvar divStyle = window.getComputedStyle( div );\n\t\tpixelPositionVal = divStyle.top !== \"1%\";\n\n\t\t// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44\n\t\treliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;\n\n\t\t// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3\n\t\t// Some styles come back with percentage values, even though they shouldn't\n\t\tdiv.style.right = \"60%\";\n\t\tpixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;\n\n\t\t// Support: IE 9 - 11 only\n\t\t// Detect misreporting of content dimensions for box-sizing:border-box elements\n\t\tboxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;\n\n\t\t// Support: IE 9 only\n\t\t// Detect overflow:scroll screwiness (gh-3699)\n\t\t// Support: Chrome <=64\n\t\t// Don't get tricked when zoom affects offsetWidth (gh-4029)\n\t\tdiv.style.position = \"absolute\";\n\t\tscrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;\n\n\t\tdocumentElement.removeChild( container );\n\n\t\t// Nullify the div so it wouldn't be stored in the memory and\n\t\t// it will also be a sign that checks already performed\n\t\tdiv = null;\n\t}\n\n\tfunction roundPixelMeasures( measure ) {\n\t\treturn Math.round( parseFloat( measure ) );\n\t}\n\n\tvar pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,\n\t\treliableTrDimensionsVal, reliableMarginLeftVal,\n\t\tcontainer = document.createElement( \"div\" ),\n\t\tdiv = document.createElement( \"div\" );\n\n\t// Finish early in limited (non-browser) environments\n\tif ( !div.style ) {\n\t\treturn;\n\t}\n\n\t// Support: IE <=9 - 11 only\n\t// Style of cloned element affects source element cloned (trac-8908)\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\tjQuery.extend( support, {\n\t\tboxSizingReliable: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn boxSizingReliableVal;\n\t\t},\n\t\tpixelBoxStyles: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn pixelBoxStylesVal;\n\t\t},\n\t\tpixelPosition: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn pixelPositionVal;\n\t\t},\n\t\treliableMarginLeft: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn reliableMarginLeftVal;\n\t\t},\n\t\tscrollboxSize: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn scrollboxSizeVal;\n\t\t},\n\n\t\t// Support: IE 9 - 11+, Edge 15 - 18+\n\t\t// IE/Edge misreport `getComputedStyle` of table rows with width/height\n\t\t// set in CSS while `offset*` properties report correct values.\n\t\t// Behavior in IE 9 is more subtle than in newer versions & it passes\n\t\t// some versions of this test; make sure not to make it pass there!\n\t\t//\n\t\t// Support: Firefox 70+\n\t\t// Only Firefox includes border widths\n\t\t// in computed dimensions. (gh-4529)\n\t\treliableTrDimensions: function() {\n\t\t\tvar table, tr, trChild, trStyle;\n\t\t\tif ( reliableTrDimensionsVal == null ) {\n\t\t\t\ttable = document.createElement( \"table\" );\n\t\t\t\ttr = document.createElement( \"tr\" );\n\t\t\t\ttrChild = document.createElement( \"div\" );\n\n\t\t\t\ttable.style.cssText = \"position:absolute;left:-11111px;border-collapse:separate\";\n\t\t\t\ttr.style.cssText = \"border:1px solid\";\n\n\t\t\t\t// Support: Chrome 86+\n\t\t\t\t// Height set through cssText does not get applied.\n\t\t\t\t// Computed height then comes back as 0.\n\t\t\t\ttr.style.height = \"1px\";\n\t\t\t\ttrChild.style.height = \"9px\";\n\n\t\t\t\t// Support: Android 8 Chrome 86+\n\t\t\t\t// In our bodyBackground.html iframe,\n\t\t\t\t// display for all div elements is set to \"inline\",\n\t\t\t\t// which causes a problem only in Android 8 Chrome 86.\n\t\t\t\t// Ensuring the div is display: block\n\t\t\t\t// gets around this issue.\n\t\t\t\ttrChild.style.display = \"block\";\n\n\t\t\t\tdocumentElement\n\t\t\t\t\t.appendChild( table )\n\t\t\t\t\t.appendChild( tr )\n\t\t\t\t\t.appendChild( trChild );\n\n\t\t\t\ttrStyle = window.getComputedStyle( tr );\n\t\t\t\treliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) +\n\t\t\t\t\tparseInt( trStyle.borderTopWidth, 10 ) +\n\t\t\t\t\tparseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight;\n\n\t\t\t\tdocumentElement.removeChild( table );\n\t\t\t}\n\t\t\treturn reliableTrDimensionsVal;\n\t\t}\n\t} );\n} )();\n\n\nfunction curCSS( elem, name, computed ) {\n\tvar width, minWidth, maxWidth, ret,\n\t\tisCustomProp = rcustomProp.test( name ),\n\n\t\t// Support: Firefox 51+\n\t\t// Retrieving style before computed somehow\n\t\t// fixes an issue with getting wrong values\n\t\t// on detached elements\n\t\tstyle = elem.style;\n\n\tcomputed = computed || getStyles( elem );\n\n\t// getPropertyValue is needed for:\n\t//   .css('filter') (IE 9 only, trac-12537)\n\t//   .css('--customProperty) (gh-3144)\n\tif ( computed ) {\n\n\t\t// Support: IE <=9 - 11+\n\t\t// IE only supports `\"float\"` in `getPropertyValue`; in computed styles\n\t\t// it's only available as `\"cssFloat\"`. We no longer modify properties\n\t\t// sent to `.css()` apart from camelCasing, so we need to check both.\n\t\t// Normally, this would create difference in behavior: if\n\t\t// `getPropertyValue` returns an empty string, the value returned\n\t\t// by `.css()` would be `undefined`. This is usually the case for\n\t\t// disconnected elements. However, in IE even disconnected elements\n\t\t// with no styles return `\"none\"` for `getPropertyValue( \"float\" )`\n\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\n\t\tif ( isCustomProp && ret ) {\n\n\t\t\t// Support: Firefox 105+, Chrome <=105+\n\t\t\t// Spec requires trimming whitespace for custom properties (gh-4926).\n\t\t\t// Firefox only trims leading whitespace. Chrome just collapses\n\t\t\t// both leading & trailing whitespace to a single space.\n\t\t\t//\n\t\t\t// Fall back to `undefined` if empty string returned.\n\t\t\t// This collapses a missing definition with property defined\n\t\t\t// and set to an empty string but there's no standard API\n\t\t\t// allowing us to differentiate them without a performance penalty\n\t\t\t// and returning `undefined` aligns with older jQuery.\n\t\t\t//\n\t\t\t// rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED\n\t\t\t// as whitespace while CSS does not, but this is not a problem\n\t\t\t// because CSS preprocessing replaces them with U+000A LINE FEED\n\t\t\t// (which *is* CSS whitespace)\n\t\t\t// https://www.w3.org/TR/css-syntax-3/#input-preprocessing\n\t\t\tret = ret.replace( rtrimCSS, \"$1\" ) || undefined;\n\t\t}\n\n\t\tif ( ret === \"\" && !isAttached( elem ) ) {\n\t\t\tret = jQuery.style( elem, name );\n\t\t}\n\n\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t// Android Browser returns percentage for some values,\n\t\t// but width seems to be reliably pixels.\n\t\t// This is against the CSSOM draft spec:\n\t\t// https://drafts.csswg.org/cssom/#resolved-values\n\t\tif ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\twidth = style.width;\n\t\t\tminWidth = style.minWidth;\n\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\tret = computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width = width;\n\t\t\tstyle.minWidth = minWidth;\n\t\t\tstyle.maxWidth = maxWidth;\n\t\t}\n\t}\n\n\treturn ret !== undefined ?\n\n\t\t// Support: IE <=9 - 11 only\n\t\t// IE returns zIndex value as an integer.\n\t\tret + \"\" :\n\t\tret;\n}\n\n\nfunction addGetHookIf( conditionFn, hookFn ) {\n\n\t// Define the hook, we'll check on the first run if it's really needed.\n\treturn {\n\t\tget: function() {\n\t\t\tif ( conditionFn() ) {\n\n\t\t\t\t// Hook not needed (or it's not possible to use it due\n\t\t\t\t// to missing dependency), remove it.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\t\t\treturn ( this.get = hookFn ).apply( this, arguments );\n\t\t}\n\t};\n}\n\n\nvar cssPrefixes = [ \"Webkit\", \"Moz\", \"ms\" ],\n\temptyStyle = document.createElement( \"div\" ).style,\n\tvendorProps = {};\n\n// Return a vendor-prefixed property or undefined\nfunction vendorPropName( name ) {\n\n\t// Check for vendor prefixed names\n\tvar capName = name[ 0 ].toUpperCase() + name.slice( 1 ),\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in emptyStyle ) {\n\t\t\treturn name;\n\t\t}\n\t}\n}\n\n// Return a potentially-mapped jQuery.cssProps or vendor prefixed property\nfunction finalPropName( name ) {\n\tvar final = jQuery.cssProps[ name ] || vendorProps[ name ];\n\n\tif ( final ) {\n\t\treturn final;\n\t}\n\tif ( name in emptyStyle ) {\n\t\treturn name;\n\t}\n\treturn vendorProps[ name ] = vendorPropName( name ) || name;\n}\n\n\nvar\n\n\t// Swappable if display is none or starts with table\n\t// except \"table\", \"table-cell\", or \"table-caption\"\n\t// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: \"0\",\n\t\tfontWeight: \"400\"\n\t};\n\nfunction setPositiveNumber( _elem, value, subtract ) {\n\n\t// Any relative (+/-) values have already been\n\t// normalized at this point\n\tvar matches = rcssNum.exec( value );\n\treturn matches ?\n\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {\n\tvar i = dimension === \"width\" ? 1 : 0,\n\t\textra = 0,\n\t\tdelta = 0;\n\n\t// Adjustment may not be necessary\n\tif ( box === ( isBorderBox ? \"border\" : \"content\" ) ) {\n\t\treturn 0;\n\t}\n\n\tfor ( ; i < 4; i += 2 ) {\n\n\t\t// Both box models exclude margin\n\t\tif ( box === \"margin\" ) {\n\t\t\tdelta += jQuery.css( elem, box + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\t// If we get here with a content-box, we're seeking \"padding\" or \"border\" or \"margin\"\n\t\tif ( !isBorderBox ) {\n\n\t\t\t// Add padding\n\t\t\tdelta += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// For \"border\" or \"margin\", add border\n\t\t\tif ( box !== \"padding\" ) {\n\t\t\t\tdelta += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\n\t\t\t// But still keep track of it otherwise\n\t\t\t} else {\n\t\t\t\textra += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\n\t\t// If we get here with a border-box (content + padding + border), we're seeking \"content\" or\n\t\t// \"padding\" or \"margin\"\n\t\t} else {\n\n\t\t\t// For \"content\", subtract padding\n\t\t\tif ( box === \"content\" ) {\n\t\t\t\tdelta -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// For \"content\" or \"padding\", subtract border\n\t\t\tif ( box !== \"margin\" ) {\n\t\t\t\tdelta -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Account for positive content-box scroll gutter when requested by providing computedVal\n\tif ( !isBorderBox && computedVal >= 0 ) {\n\n\t\t// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border\n\t\t// Assuming integer scroll gutter, subtract the rest and round down\n\t\tdelta += Math.max( 0, Math.ceil(\n\t\t\telem[ \"offset\" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -\n\t\t\tcomputedVal -\n\t\t\tdelta -\n\t\t\textra -\n\t\t\t0.5\n\n\t\t// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter\n\t\t// Use an explicit zero to avoid NaN (gh-3964)\n\t\t) ) || 0;\n\t}\n\n\treturn delta;\n}\n\nfunction getWidthOrHeight( elem, dimension, extra ) {\n\n\t// Start with computed style\n\tvar styles = getStyles( elem ),\n\n\t\t// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).\n\t\t// Fake content-box until we know it's needed to know the true value.\n\t\tboxSizingNeeded = !support.boxSizingReliable() || extra,\n\t\tisBorderBox = boxSizingNeeded &&\n\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\tvalueIsBorderBox = isBorderBox,\n\n\t\tval = curCSS( elem, dimension, styles ),\n\t\toffsetProp = \"offset\" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );\n\n\t// Support: Firefox <=54\n\t// Return a confounding non-pixel value or feign ignorance, as appropriate.\n\tif ( rnumnonpx.test( val ) ) {\n\t\tif ( !extra ) {\n\t\t\treturn val;\n\t\t}\n\t\tval = \"auto\";\n\t}\n\n\n\t// Support: IE 9 - 11 only\n\t// Use offsetWidth/offsetHeight for when box sizing is unreliable.\n\t// In those cases, the computed value can be trusted to be border-box.\n\tif ( ( !support.boxSizingReliable() && isBorderBox ||\n\n\t\t// Support: IE 10 - 11+, Edge 15 - 18+\n\t\t// IE/Edge misreport `getComputedStyle` of table rows with width/height\n\t\t// set in CSS while `offset*` properties report correct values.\n\t\t// Interestingly, in some cases IE 9 doesn't suffer from this issue.\n\t\t!support.reliableTrDimensions() && nodeName( elem, \"tr\" ) ||\n\n\t\t// Fall back to offsetWidth/offsetHeight when value is \"auto\"\n\t\t// This happens for inline elements with no explicit setting (gh-3571)\n\t\tval === \"auto\" ||\n\n\t\t// Support: Android <=4.1 - 4.3 only\n\t\t// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)\n\t\t!parseFloat( val ) && jQuery.css( elem, \"display\", false, styles ) === \"inline\" ) &&\n\n\t\t// Make sure the element is visible & connected\n\t\telem.getClientRects().length ) {\n\n\t\tisBorderBox = jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t\t// Where available, offsetWidth/offsetHeight approximate border box dimensions.\n\t\t// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the\n\t\t// retrieved value as a content box dimension.\n\t\tvalueIsBorderBox = offsetProp in elem;\n\t\tif ( valueIsBorderBox ) {\n\t\t\tval = elem[ offsetProp ];\n\t\t}\n\t}\n\n\t// Normalize \"\" and auto\n\tval = parseFloat( val ) || 0;\n\n\t// Adjust for the element's box model\n\treturn ( val +\n\t\tboxModelAdjustment(\n\t\t\telem,\n\t\t\tdimension,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles,\n\n\t\t\t// Provide the current computed size to request scroll gutter calculation (gh-3589)\n\t\t\tval\n\t\t)\n\t) + \"px\";\n}\n\njQuery.extend( {\n\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"animationIterationCount\": true,\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"flexGrow\": true,\n\t\t\"flexShrink\": true,\n\t\t\"fontWeight\": true,\n\t\t\"gridArea\": true,\n\t\t\"gridColumn\": true,\n\t\t\"gridColumnEnd\": true,\n\t\t\"gridColumnStart\": true,\n\t\t\"gridRow\": true,\n\t\t\"gridRowEnd\": true,\n\t\t\"gridRowStart\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = camelCase( name ),\n\t\t\tisCustomProp = rcustomProp.test( name ),\n\t\t\tstyle = elem.style;\n\n\t\t// Make sure that we're working with the right name. We don't\n\t\t// want to query the value if it is a CSS custom property\n\t\t// since they are user-defined.\n\t\tif ( !isCustomProp ) {\n\t\t\tname = finalPropName( origName );\n\t\t}\n\n\t\t// Gets hook for the prefixed version, then unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// Convert \"+=\" or \"-=\" to relative numbers (trac-7345)\n\t\t\tif ( type === \"string\" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {\n\t\t\t\tvalue = adjustCSS( elem, name, ret );\n\n\t\t\t\t// Fixes bug trac-9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren't set (trac-7116)\n\t\t\tif ( value == null || value !== value ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add the unit (except for certain CSS properties)\n\t\t\t// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append\n\t\t\t// \"px\" to a few hardcoded values.\n\t\t\tif ( type === \"number\" && !isCustomProp ) {\n\t\t\t\tvalue += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? \"\" : \"px\" );\n\t\t\t}\n\n\t\t\t// background-* props affect original clone's values\n\t\t\tif ( !support.clearCloneStyle && value === \"\" && name.indexOf( \"background\" ) === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !( \"set\" in hooks ) ||\n\t\t\t\t( value = hooks.set( elem, value, extra ) ) !== undefined ) {\n\n\t\t\t\tif ( isCustomProp ) {\n\t\t\t\t\tstyle.setProperty( name, value );\n\t\t\t\t} else {\n\t\t\t\t\tstyle[ name ] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks &&\n\t\t\t\t( ret = hooks.get( elem, false, extra ) ) !== undefined ) {\n\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = camelCase( name ),\n\t\t\tisCustomProp = rcustomProp.test( name );\n\n\t\t// Make sure that we're working with the right name. We don't\n\t\t// want to modify the value if it is a CSS custom property\n\t\t// since they are user-defined.\n\t\tif ( !isCustomProp ) {\n\t\t\tname = finalPropName( origName );\n\t\t}\n\n\t\t// Try prefixed name followed by the unprefixed name\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t// Convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Make numeric if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || isFinite( num ) ? num || 0 : val;\n\t\t}\n\n\t\treturn val;\n\t}\n} );\n\njQuery.each( [ \"height\", \"width\" ], function( _i, dimension ) {\n\tjQuery.cssHooks[ dimension ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\n\t\t\t\t// Certain elements can have dimension info if we invisibly show them\n\t\t\t\t// but it must have a current display style that would benefit\n\t\t\t\treturn rdisplayswap.test( jQuery.css( elem, \"display\" ) ) &&\n\n\t\t\t\t\t// Support: Safari 8+\n\t\t\t\t\t// Table columns in Safari have non-zero offsetWidth & zero\n\t\t\t\t\t// getBoundingClientRect().width unless display is changed.\n\t\t\t\t\t// Support: IE <=11 only\n\t\t\t\t\t// Running getBoundingClientRect on a disconnected node\n\t\t\t\t\t// in IE throws an error.\n\t\t\t\t\t( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?\n\t\t\t\t\tswap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, dimension, extra );\n\t\t\t\t\t} ) :\n\t\t\t\t\tgetWidthOrHeight( elem, dimension, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar matches,\n\t\t\t\tstyles = getStyles( elem ),\n\n\t\t\t\t// Only read styles.position if the test has a chance to fail\n\t\t\t\t// to avoid forcing a reflow.\n\t\t\t\tscrollboxSizeBuggy = !support.scrollboxSize() &&\n\t\t\t\t\tstyles.position === \"absolute\",\n\n\t\t\t\t// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)\n\t\t\t\tboxSizingNeeded = scrollboxSizeBuggy || extra,\n\t\t\t\tisBorderBox = boxSizingNeeded &&\n\t\t\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\tsubtract = extra ?\n\t\t\t\t\tboxModelAdjustment(\n\t\t\t\t\t\telem,\n\t\t\t\t\t\tdimension,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\tisBorderBox,\n\t\t\t\t\t\tstyles\n\t\t\t\t\t) :\n\t\t\t\t\t0;\n\n\t\t\t// Account for unreliable border-box dimensions by comparing offset* to computed and\n\t\t\t// faking a content-box to get border and padding (gh-3699)\n\t\t\tif ( isBorderBox && scrollboxSizeBuggy ) {\n\t\t\t\tsubtract -= Math.ceil(\n\t\t\t\t\telem[ \"offset\" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -\n\t\t\t\t\tparseFloat( styles[ dimension ] ) -\n\t\t\t\t\tboxModelAdjustment( elem, dimension, \"border\", false, styles ) -\n\t\t\t\t\t0.5\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Convert to pixels if value adjustment is needed\n\t\t\tif ( subtract && ( matches = rcssNum.exec( value ) ) &&\n\t\t\t\t( matches[ 3 ] || \"px\" ) !== \"px\" ) {\n\n\t\t\t\telem.style[ dimension ] = value;\n\t\t\t\tvalue = jQuery.css( elem, dimension );\n\t\t\t}\n\n\t\t\treturn setPositiveNumber( elem, value, subtract );\n\t\t}\n\t};\n} );\n\njQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,\n\tfunction( elem, computed ) {\n\t\tif ( computed ) {\n\t\t\treturn ( parseFloat( curCSS( elem, \"marginLeft\" ) ) ||\n\t\t\t\telem.getBoundingClientRect().left -\n\t\t\t\t\tswap( elem, { marginLeft: 0 }, function() {\n\t\t\t\t\t\treturn elem.getBoundingClientRect().left;\n\t\t\t\t\t} )\n\t\t\t) + \"px\";\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each( {\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// Assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split( \" \" ) : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( prefix !== \"margin\" ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n} );\n\njQuery.fn.extend( {\n\tcss: function( name, value ) {\n\t\treturn access( this, function( elem, name, value ) {\n\t\t\tvar styles, len,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( Array.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t}\n} );\n\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || jQuery.easing._default;\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\t// Use a property on the element directly when it is not a DOM element,\n\t\t\t// or when there is no matching style property that exists.\n\t\t\tif ( tween.elem.nodeType !== 1 ||\n\t\t\t\ttween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// Passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails.\n\t\t\t// Simple values such as \"10px\" are parsed to Float;\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as-is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\n\t\t\t// Use step hook for back compat.\n\t\t\t// Use cssHook if its there.\n\t\t\t// Use .style if available and use plain properties where available.\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.nodeType === 1 && (\n\t\t\t\tjQuery.cssHooks[ tween.prop ] ||\n\t\t\t\t\ttween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE <=9 only\n// Panic based approach to setting things on disconnected nodes\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p * Math.PI ) / 2;\n\t},\n\t_default: \"swing\"\n};\n\njQuery.fx = Tween.prototype.init;\n\n// Back compat <1.8 extension point\njQuery.fx.step = {};\n\n\n\n\nvar\n\tfxNow, inProgress,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trrun = /queueHooks$/;\n\nfunction schedule() {\n\tif ( inProgress ) {\n\t\tif ( document.hidden === false && window.requestAnimationFrame ) {\n\t\t\twindow.requestAnimationFrame( schedule );\n\t\t} else {\n\t\t\twindow.setTimeout( schedule, jQuery.fx.interval );\n\t\t}\n\n\t\tjQuery.fx.tick();\n\t}\n}\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\twindow.setTimeout( function() {\n\t\tfxNow = undefined;\n\t} );\n\treturn ( fxNow = Date.now() );\n}\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\ti = 0,\n\t\tattrs = { height: type };\n\n\t// If we include width, step value is 1 to do all cssExpand values,\n\t// otherwise step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth ? 1 : 0;\n\tfor ( ; i < 4; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {\n\n\t\t\t// We're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter( elem, props, opts ) {\n\tvar prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,\n\t\tisBox = \"width\" in props || \"height\" in props,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHiddenWithinTree( elem ),\n\t\tdataShow = dataPriv.get( elem, \"fxshow\" );\n\n\t// Queue-skipping animations hijack the fx hooks\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always( function() {\n\n\t\t\t// Ensure the complete handler is called before this completes\n\t\t\tanim.always( function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\t}\n\n\t// Detect show/hide animations\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.test( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\n\t\t\t\t// Pretend to be hidden if this is a \"show\" and\n\t\t\t\t// there is still data from a stopped show/hide\n\t\t\t\tif ( value === \"show\" && dataShow && dataShow[ prop ] !== undefined ) {\n\t\t\t\t\thidden = true;\n\n\t\t\t\t// Ignore all other no-op show/hide data\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\t\t}\n\t}\n\n\t// Bail out if this is a no-op like .hide().hide()\n\tpropTween = !jQuery.isEmptyObject( props );\n\tif ( !propTween && jQuery.isEmptyObject( orig ) ) {\n\t\treturn;\n\t}\n\n\t// Restrict \"overflow\" and \"display\" styles during box animations\n\tif ( isBox && elem.nodeType === 1 ) {\n\n\t\t// Support: IE <=9 - 11, Edge 12 - 15\n\t\t// Record all 3 overflow attributes because IE does not infer the shorthand\n\t\t// from identically-valued overflowX and overflowY and Edge just mirrors\n\t\t// the overflowX value there.\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Identify a display type, preferring old show/hide data over the CSS cascade\n\t\trestoreDisplay = dataShow && dataShow.display;\n\t\tif ( restoreDisplay == null ) {\n\t\t\trestoreDisplay = dataPriv.get( elem, \"display\" );\n\t\t}\n\t\tdisplay = jQuery.css( elem, \"display\" );\n\t\tif ( display === \"none\" ) {\n\t\t\tif ( restoreDisplay ) {\n\t\t\t\tdisplay = restoreDisplay;\n\t\t\t} else {\n\n\t\t\t\t// Get nonempty value(s) by temporarily forcing visibility\n\t\t\t\tshowHide( [ elem ], true );\n\t\t\t\trestoreDisplay = elem.style.display || restoreDisplay;\n\t\t\t\tdisplay = jQuery.css( elem, \"display\" );\n\t\t\t\tshowHide( [ elem ] );\n\t\t\t}\n\t\t}\n\n\t\t// Animate inline elements as inline-block\n\t\tif ( display === \"inline\" || display === \"inline-block\" && restoreDisplay != null ) {\n\t\t\tif ( jQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\t\t// Restore the original display value at the end of pure show/hide animations\n\t\t\t\tif ( !propTween ) {\n\t\t\t\t\tanim.done( function() {\n\t\t\t\t\t\tstyle.display = restoreDisplay;\n\t\t\t\t\t} );\n\t\t\t\t\tif ( restoreDisplay == null ) {\n\t\t\t\t\t\tdisplay = style.display;\n\t\t\t\t\t\trestoreDisplay = display === \"none\" ? \"\" : display;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstyle.display = \"inline-block\";\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tanim.always( function() {\n\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t} );\n\t}\n\n\t// Implement show/hide animations\n\tpropTween = false;\n\tfor ( prop in orig ) {\n\n\t\t// General show/hide setup for this element animation\n\t\tif ( !propTween ) {\n\t\t\tif ( dataShow ) {\n\t\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\t\thidden = dataShow.hidden;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdataShow = dataPriv.access( elem, \"fxshow\", { display: restoreDisplay } );\n\t\t\t}\n\n\t\t\t// Store hidden/visible for toggle so `.stop().toggle()` \"reverses\"\n\t\t\tif ( toggle ) {\n\t\t\t\tdataShow.hidden = !hidden;\n\t\t\t}\n\n\t\t\t// Show elements before animating them\n\t\t\tif ( hidden ) {\n\t\t\t\tshowHide( [ elem ], true );\n\t\t\t}\n\n\t\t\t/* eslint-disable no-loop-func */\n\n\t\t\tanim.done( function() {\n\n\t\t\t\t/* eslint-enable no-loop-func */\n\n\t\t\t\t// The final step of a \"hide\" animation is actually hiding the element\n\t\t\t\tif ( !hidden ) {\n\t\t\t\t\tshowHide( [ elem ] );\n\t\t\t\t}\n\t\t\t\tdataPriv.remove( elem, \"fxshow\" );\n\t\t\t\tfor ( prop in orig ) {\n\t\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\t// Per-property setup\n\t\tpropTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\t\tif ( !( prop in dataShow ) ) {\n\t\t\tdataShow[ prop ] = propTween.start;\n\t\t\tif ( hidden ) {\n\t\t\t\tpropTween.end = propTween.start;\n\t\t\t\tpropTween.start = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( Array.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// Not quite $.extend, this won't overwrite existing keys.\n\t\t\t// Reusing 'index' because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = Animation.prefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\n\t\t\t// Don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t} ),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\n\t\t\t\t// Support: Android 2.3 only\n\t\t\t\t// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ] );\n\n\t\t\t// If there's more to do, yield\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t}\n\n\t\t\t// If this was an empty animation, synthesize a final progress notification\n\t\t\tif ( !length ) {\n\t\t\t\tdeferred.notifyWith( elem, [ animation, 1, 0 ] );\n\t\t\t}\n\n\t\t\t// Resolve the animation and report its conclusion\n\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\treturn false;\n\t\t},\n\t\tanimation = deferred.promise( {\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, {\n\t\t\t\tspecialEasing: {},\n\t\t\t\teasing: jQuery.easing._default\n\t\t\t}, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\n\t\t\t\t\t// If we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// Resolve when we played the last frame; otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.notifyWith( elem, [ animation, 1, 0 ] );\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t} ),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length; index++ ) {\n\t\tresult = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\tif ( isFunction( result.stop ) ) {\n\t\t\t\tjQuery._queueHooks( animation.elem, animation.opts.queue ).stop =\n\t\t\t\t\tresult.stop.bind( result );\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\t// Attach callbacks from options\n\tanimation\n\t\t.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t} )\n\t);\n\n\treturn animation;\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweeners: {\n\t\t\"*\": [ function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value );\n\t\t\tadjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );\n\t\t\treturn tween;\n\t\t} ]\n\t},\n\n\ttweener: function( props, callback ) {\n\t\tif ( isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.match( rnothtmlwhite );\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\tAnimation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];\n\t\t\tAnimation.tweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilters: [ defaultPrefilter ],\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tAnimation.prefilters.unshift( callback );\n\t\t} else {\n\t\t\tAnimation.prefilters.push( callback );\n\t\t}\n\t}\n} );\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tisFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !isFunction( easing ) && easing\n\t};\n\n\t// Go to the end state if fx are off\n\tif ( jQuery.fx.off ) {\n\t\topt.duration = 0;\n\n\t} else {\n\t\tif ( typeof opt.duration !== \"number\" ) {\n\t\t\tif ( opt.duration in jQuery.fx.speeds ) {\n\t\t\t\topt.duration = jQuery.fx.speeds[ opt.duration ];\n\n\t\t\t} else {\n\t\t\t\topt.duration = jQuery.fx.speeds._default;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend( {\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// Show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHiddenWithinTree ).css( \"opacity\", 0 ).show()\n\n\t\t\t// Animate to the value specified\n\t\t\t.end().animate( { opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || dataPriv.get( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\n\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = dataPriv.get( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this &&\n\t\t\t\t\t( type == null || timers[ index ].queue === type ) ) {\n\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Start the next in the queue if the last step wasn't forced.\n\t\t\t// Timers currently will call their complete callbacks, which\n\t\t\t// will dequeue but only if they were gotoEnd.\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t} );\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tvar index,\n\t\t\t\tdata = dataPriv.get( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// Enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// Empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// Look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t} );\n\t}\n} );\n\njQuery.each( [ \"toggle\", \"show\", \"hide\" ], function( _i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n} );\n\n// Generate shortcuts for custom animations\njQuery.each( {\n\tslideDown: genFx( \"show\" ),\n\tslideUp: genFx( \"hide\" ),\n\tslideToggle: genFx( \"toggle\" ),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n} );\n\njQuery.timers = [];\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ti = 0,\n\t\ttimers = jQuery.timers;\n\n\tfxNow = Date.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\n\t\t// Run the timer and safely remove it when done (allowing for external removal)\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tjQuery.timers.push( timer );\n\tjQuery.fx.start();\n};\n\njQuery.fx.interval = 13;\njQuery.fx.start = function() {\n\tif ( inProgress ) {\n\t\treturn;\n\t}\n\n\tinProgress = true;\n\tschedule();\n};\n\njQuery.fx.stop = function() {\n\tinProgress = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\njQuery.fn.delay = function( time, type ) {\n\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\ttype = type || \"fx\";\n\n\treturn this.queue( type, function( next, hooks ) {\n\t\tvar timeout = window.setTimeout( next, time );\n\t\thooks.stop = function() {\n\t\t\twindow.clearTimeout( timeout );\n\t\t};\n\t} );\n};\n\n\n( function() {\n\tvar input = document.createElement( \"input\" ),\n\t\tselect = document.createElement( \"select\" ),\n\t\topt = select.appendChild( document.createElement( \"option\" ) );\n\n\tinput.type = \"checkbox\";\n\n\t// Support: Android <=4.3 only\n\t// Default value for a checkbox should be \"on\"\n\tsupport.checkOn = input.value !== \"\";\n\n\t// Support: IE <=11 only\n\t// Must access selectedIndex to make default options select\n\tsupport.optSelected = opt.selected;\n\n\t// Support: IE <=11 only\n\t// An input loses its value after becoming a radio\n\tinput = document.createElement( \"input\" );\n\tinput.value = \"t\";\n\tinput.type = \"radio\";\n\tsupport.radioValue = input.value === \"t\";\n} )();\n\n\nvar boolHook,\n\tattrHandle = jQuery.expr.attrHandle;\n\njQuery.fn.extend( {\n\tattr: function( name, value ) {\n\t\treturn access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each( function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t} );\n\t}\n} );\n\njQuery.extend( {\n\tattr: function( elem, name, value ) {\n\t\tvar ret, hooks,\n\t\t\tnType = elem.nodeType;\n\n\t\t// Don't get/set attributes on text, comment and attribute nodes\n\t\tif ( nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === \"undefined\" ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// Attribute hooks are determined by the lowercase version\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\thooks = jQuery.attrHooks[ name.toLowerCase() ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( hooks && \"set\" in hooks &&\n\t\t\t\t( ret = hooks.set( elem, value, name ) ) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\treturn value;\n\t\t}\n\n\t\tif ( hooks && \"get\" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tret = jQuery.find.attr( elem, name );\n\n\t\t// Non-existent attributes return null, we normalize to undefined\n\t\treturn ret == null ? undefined : ret;\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !support.radioValue && value === \"radio\" &&\n\t\t\t\t\tnodeName( elem, \"input\" ) ) {\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name,\n\t\t\ti = 0,\n\n\t\t\t// Attribute names can contain non-HTML whitespace characters\n\t\t\t// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2\n\t\t\tattrNames = value && value.match( rnothtmlwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( ( name = attrNames[ i++ ] ) ) {\n\t\t\t\telem.removeAttribute( name );\n\t\t\t}\n\t\t}\n\t}\n} );\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\telem.setAttribute( name, name );\n\t\t}\n\t\treturn name;\n\t}\n};\n\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( _i, name ) {\n\tvar getter = attrHandle[ name ] || jQuery.find.attr;\n\n\tattrHandle[ name ] = function( elem, name, isXML ) {\n\t\tvar ret, handle,\n\t\t\tlowercaseName = name.toLowerCase();\n\n\t\tif ( !isXML ) {\n\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle = attrHandle[ lowercaseName ];\n\t\t\tattrHandle[ lowercaseName ] = ret;\n\t\t\tret = getter( elem, name, isXML ) != null ?\n\t\t\t\tlowercaseName :\n\t\t\t\tnull;\n\t\t\tattrHandle[ lowercaseName ] = handle;\n\t\t}\n\t\treturn ret;\n\t};\n} );\n\n\n\n\nvar rfocusable = /^(?:input|select|textarea|button)$/i,\n\trclickable = /^(?:a|area)$/i;\n\njQuery.fn.extend( {\n\tprop: function( name, value ) {\n\t\treturn access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\treturn this.each( function() {\n\t\t\tdelete this[ jQuery.propFix[ name ] || name ];\n\t\t} );\n\t}\n} );\n\njQuery.extend( {\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks,\n\t\t\tnType = elem.nodeType;\n\n\t\t// Don't get/set properties on text, comment and attribute nodes\n\t\tif ( nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\tif ( hooks && \"set\" in hooks &&\n\t\t\t\t( ret = hooks.set( elem, value, name ) ) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\treturn ( elem[ name ] = value );\n\t\t}\n\n\t\tif ( hooks && \"get\" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {\n\t\t\treturn ret;\n\t\t}\n\n\t\treturn elem[ name ];\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\n\t\t\t\t// Support: IE <=9 - 11 only\n\t\t\t\t// elem.tabIndex doesn't always return the\n\t\t\t\t// correct value when it hasn't been explicitly set\n\t\t\t\t// Use proper attribute retrieval (trac-12072)\n\t\t\t\tvar tabindex = jQuery.find.attr( elem, \"tabindex\" );\n\n\t\t\t\tif ( tabindex ) {\n\t\t\t\t\treturn parseInt( tabindex, 10 );\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\trfocusable.test( elem.nodeName ) ||\n\t\t\t\t\trclickable.test( elem.nodeName ) &&\n\t\t\t\t\telem.href\n\t\t\t\t) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t},\n\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t}\n} );\n\n// Support: IE <=11 only\n// Accessing the selectedIndex property\n// forces the browser to respect setting selected\n// on the option\n// The getter ensures a default option is selected\n// when in an optgroup\n// eslint rule \"no-unused-expressions\" is disabled for this code\n// since it considers such accessions noop\nif ( !support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\n\t\t\t/* eslint no-unused-expressions: \"off\" */\n\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent && parent.parentNode ) {\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t\tset: function( elem ) {\n\n\t\t\t/* eslint no-unused-expressions: \"off\" */\n\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent ) {\n\t\t\t\tparent.selectedIndex;\n\n\t\t\t\tif ( parent.parentNode ) {\n\t\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\njQuery.each( [\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n} );\n\n\n\n\n\t// Strip and collapse whitespace according to HTML spec\n\t// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace\n\tfunction stripAndCollapse( value ) {\n\t\tvar tokens = value.match( rnothtmlwhite ) || [];\n\t\treturn tokens.join( \" \" );\n\t}\n\n\nfunction getClass( elem ) {\n\treturn elem.getAttribute && elem.getAttribute( \"class\" ) || \"\";\n}\n\nfunction classesToArray( value ) {\n\tif ( Array.isArray( value ) ) {\n\t\treturn value;\n\t}\n\tif ( typeof value === \"string\" ) {\n\t\treturn value.match( rnothtmlwhite ) || [];\n\t}\n\treturn [];\n}\n\njQuery.fn.extend( {\n\taddClass: function( value ) {\n\t\tvar classNames, cur, curValue, className, i, finalValue;\n\n\t\tif ( isFunction( value ) ) {\n\t\t\treturn this.each( function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, getClass( this ) ) );\n\t\t\t} );\n\t\t}\n\n\t\tclassNames = classesToArray( value );\n\n\t\tif ( classNames.length ) {\n\t\t\treturn this.each( function() {\n\t\t\t\tcurValue = getClass( this );\n\t\t\t\tcur = this.nodeType === 1 && ( \" \" + stripAndCollapse( curValue ) + \" \" );\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tfor ( i = 0; i < classNames.length; i++ ) {\n\t\t\t\t\t\tclassName = classNames[ i ];\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + className + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += className + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = stripAndCollapse( cur );\n\t\t\t\t\tif ( curValue !== finalValue ) {\n\t\t\t\t\t\tthis.setAttribute( \"class\", finalValue );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classNames, cur, curValue, className, i, finalValue;\n\n\t\tif ( isFunction( value ) ) {\n\t\t\treturn this.each( function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );\n\t\t\t} );\n\t\t}\n\n\t\tif ( !arguments.length ) {\n\t\t\treturn this.attr( \"class\", \"\" );\n\t\t}\n\n\t\tclassNames = classesToArray( value );\n\n\t\tif ( classNames.length ) {\n\t\t\treturn this.each( function() {\n\t\t\t\tcurValue = getClass( this );\n\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = this.nodeType === 1 && ( \" \" + stripAndCollapse( curValue ) + \" \" );\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tfor ( i = 0; i < classNames.length; i++ ) {\n\t\t\t\t\t\tclassName = classNames[ i ];\n\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + className + \" \" ) > -1 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + className + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = stripAndCollapse( cur );\n\t\t\t\t\tif ( curValue !== finalValue ) {\n\t\t\t\t\t\tthis.setAttribute( \"class\", finalValue );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar classNames, className, i, self,\n\t\t\ttype = typeof value,\n\t\t\tisValidValue = type === \"string\" || Array.isArray( value );\n\n\t\tif ( isFunction( value ) ) {\n\t\t\treturn this.each( function( i ) {\n\t\t\t\tjQuery( this ).toggleClass(\n\t\t\t\t\tvalue.call( this, i, getClass( this ), stateVal ),\n\t\t\t\t\tstateVal\n\t\t\t\t);\n\t\t\t} );\n\t\t}\n\n\t\tif ( typeof stateVal === \"boolean\" && isValidValue ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tclassNames = classesToArray( value );\n\n\t\treturn this.each( function() {\n\t\t\tif ( isValidValue ) {\n\n\t\t\t\t// Toggle individual class names\n\t\t\t\tself = jQuery( this );\n\n\t\t\t\tfor ( i = 0; i < classNames.length; i++ ) {\n\t\t\t\t\tclassName = classNames[ i ];\n\n\t\t\t\t\t// Check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( value === undefined || type === \"boolean\" ) {\n\t\t\t\tclassName = getClass( this );\n\t\t\t\tif ( className ) {\n\n\t\t\t\t\t// Store className if set\n\t\t\t\t\tdataPriv.set( this, \"__className__\", className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed `false`,\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tif ( this.setAttribute ) {\n\t\t\t\t\tthis.setAttribute( \"class\",\n\t\t\t\t\t\tclassName || value === false ?\n\t\t\t\t\t\t\t\"\" :\n\t\t\t\t\t\t\tdataPriv.get( this, \"__className__\" ) || \"\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className, elem,\n\t\t\ti = 0;\n\n\t\tclassName = \" \" + selector + \" \";\n\t\twhile ( ( elem = this[ i++ ] ) ) {\n\t\t\tif ( elem.nodeType === 1 &&\n\t\t\t\t( \" \" + stripAndCollapse( getClass( elem ) ) + \" \" ).indexOf( className ) > -1 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n} );\n\n\n\n\nvar rreturn = /\\r/g;\n\njQuery.fn.extend( {\n\tval: function( value ) {\n\t\tvar hooks, ret, valueIsFunction,\n\t\t\telem = this[ 0 ];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] ||\n\t\t\t\t\tjQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks &&\n\t\t\t\t\t\"get\" in hooks &&\n\t\t\t\t\t( ret = hooks.get( elem, \"value\" ) ) !== undefined\n\t\t\t\t) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\t// Handle most common string cases\n\t\t\t\tif ( typeof ret === \"string\" ) {\n\t\t\t\t\treturn ret.replace( rreturn, \"\" );\n\t\t\t\t}\n\n\t\t\t\t// Handle cases where value is null/undef or number\n\t\t\t\treturn ret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tvalueIsFunction = isFunction( value );\n\n\t\treturn this.each( function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( valueIsFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\n\t\t\t} else if ( Array.isArray( val ) ) {\n\t\t\t\tval = jQuery.map( val, function( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !( \"set\" in hooks ) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t} );\n\t}\n} );\n\njQuery.extend( {\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\n\t\t\t\treturn val != null ?\n\t\t\t\t\tval :\n\n\t\t\t\t\t// Support: IE <=10 - 11 only\n\t\t\t\t\t// option.text throws exceptions (trac-14686, trac-14858)\n\t\t\t\t\t// Strip and collapse whitespace\n\t\t\t\t\t// https://html.spec.whatwg.org/#strip-and-collapse-whitespace\n\t\t\t\t\tstripAndCollapse( jQuery.text( elem ) );\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option, i,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\",\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length;\n\n\t\t\t\tif ( index < 0 ) {\n\t\t\t\t\ti = max;\n\n\t\t\t\t} else {\n\t\t\t\t\ti = one ? index : 0;\n\t\t\t\t}\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t// IE8-9 doesn't update selected after form reset (trac-2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t!option.disabled &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled ||\n\t\t\t\t\t\t\t\t!nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t/* eslint-disable no-cond-assign */\n\n\t\t\t\t\tif ( option.selected =\n\t\t\t\t\t\tjQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1\n\t\t\t\t\t) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* eslint-enable no-cond-assign */\n\t\t\t\t}\n\n\t\t\t\t// Force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n} );\n\n// Radios and checkboxes getter/setter\njQuery.each( [ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( Array.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\treturn elem.getAttribute( \"value\" ) === null ? \"on\" : elem.value;\n\t\t};\n\t}\n} );\n\n\n\n\n// Return jQuery for attributes-only inclusion\n\n\nsupport.focusin = \"onfocusin\" in window;\n\n\nvar rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\tstopPropagationCallback = function( e ) {\n\t\te.stopPropagation();\n\t};\n\njQuery.extend( jQuery.event, {\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special, lastElement,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split( \".\" ) : [];\n\n\t\tcur = lastElement = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf( \".\" ) > -1 ) {\n\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split( \".\" );\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf( \":\" ) < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join( \".\" );\n\t\tevent.rnamespace = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join( \"\\\\.(?:.*\\\\.|)\" ) + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (trac-9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === ( elem.ownerDocument || document ) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {\n\t\t\tlastElement = cur;\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( dataPriv.get( cur, \"events\" ) || Object.create( null ) )[ event.type ] &&\n\t\t\t\tdataPriv.get( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( ( !special._default ||\n\t\t\t\tspecial._default.apply( eventPath.pop(), data ) === false ) &&\n\t\t\t\tacceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name as the event.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (trac-6170)\n\t\t\t\tif ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\n\t\t\t\t\tif ( event.isPropagationStopped() ) {\n\t\t\t\t\t\tlastElement.addEventListener( type, stopPropagationCallback );\n\t\t\t\t\t}\n\n\t\t\t\t\telem[ type ]();\n\n\t\t\t\t\tif ( event.isPropagationStopped() ) {\n\t\t\t\t\t\tlastElement.removeEventListener( type, stopPropagationCallback );\n\t\t\t\t\t}\n\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\t// Piggyback on a donor event to simulate a different one\n\t// Used only for `focus(in | out)` events\n\tsimulate: function( type, elem, event ) {\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true\n\t\t\t}\n\t\t);\n\n\t\tjQuery.event.trigger( e, null, elem );\n\t}\n\n} );\n\njQuery.fn.extend( {\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t} );\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[ 0 ];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n} );\n\n\n// Support: Firefox <=44\n// Firefox doesn't have focus(in | out) events\n// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787\n//\n// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1\n// focus(in | out) events fire after focus & blur events,\n// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order\n// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857\nif ( !support.focusin ) {\n\tjQuery.each( { focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler on the document while someone wants focusin/focusout\n\t\tvar handler = function( event ) {\n\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );\n\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\n\t\t\t\t// Handle: regular nodes (via `this.ownerDocument`), window\n\t\t\t\t// (via `this.document`) & document (via `this`).\n\t\t\t\tvar doc = this.ownerDocument || this.document || this,\n\t\t\t\t\tattaches = dataPriv.access( doc, fix );\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t\tdataPriv.access( doc, fix, ( attaches || 0 ) + 1 );\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tvar doc = this.ownerDocument || this.document || this,\n\t\t\t\t\tattaches = dataPriv.access( doc, fix ) - 1;\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.removeEventListener( orig, handler, true );\n\t\t\t\t\tdataPriv.remove( doc, fix );\n\n\t\t\t\t} else {\n\t\t\t\t\tdataPriv.access( doc, fix, attaches );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t} );\n}\nvar location = window.location;\n\nvar nonce = { guid: Date.now() };\n\nvar rquery = ( /\\?/ );\n\n\n\n// Cross-browser xml parsing\njQuery.parseXML = function( data ) {\n\tvar xml, parserErrorElem;\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\n\t// Support: IE 9 - 11 only\n\t// IE throws on parseFromString with invalid input.\n\ttry {\n\t\txml = ( new window.DOMParser() ).parseFromString( data, \"text/xml\" );\n\t} catch ( e ) {}\n\n\tparserErrorElem = xml && xml.getElementsByTagName( \"parsererror\" )[ 0 ];\n\tif ( !xml || parserErrorElem ) {\n\t\tjQuery.error( \"Invalid XML: \" + (\n\t\t\tparserErrorElem ?\n\t\t\t\tjQuery.map( parserErrorElem.childNodes, function( el ) {\n\t\t\t\t\treturn el.textContent;\n\t\t\t\t} ).join( \"\\n\" ) :\n\t\t\t\tdata\n\t\t) );\n\t}\n\treturn xml;\n};\n\n\nvar\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( Array.isArray( obj ) ) {\n\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams(\n\t\t\t\t\tprefix + \"[\" + ( typeof v === \"object\" && v != null ? i : \"\" ) + \"]\",\n\t\t\t\t\tv,\n\t\t\t\t\ttraditional,\n\t\t\t\t\tadd\n\t\t\t\t);\n\t\t\t}\n\t\t} );\n\n\t} else if ( !traditional && toType( obj ) === \"object\" ) {\n\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, valueOrFunction ) {\n\n\t\t\t// If value is a function, invoke it and use its return value\n\t\t\tvar value = isFunction( valueOrFunction ) ?\n\t\t\t\tvalueOrFunction() :\n\t\t\t\tvalueOrFunction;\n\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" +\n\t\t\t\tencodeURIComponent( value == null ? \"\" : value );\n\t\t};\n\n\tif ( a == null ) {\n\t\treturn \"\";\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t} );\n\n\t} else {\n\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" );\n};\n\njQuery.fn.extend( {\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map( function() {\n\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t} ).filter( function() {\n\t\t\tvar type = this.type;\n\n\t\t\t// Use .is( \":disabled\" ) so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !rcheckableType.test( type ) );\n\t\t} ).map( function( _i, elem ) {\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\tif ( val == null ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif ( Array.isArray( val ) ) {\n\t\t\t\treturn jQuery.map( val, function( val ) {\n\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t} ).get();\n\t}\n} );\n\n\nvar\n\tr20 = /%20/g,\n\trhash = /#.*$/,\n\trantiCache = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\n\t// trac-7653, trac-8125, trac-8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat( \"*\" ),\n\n\t// Anchor tag for parsing the document origin\n\toriginAnchor = document.createElement( \"a\" );\n\noriginAnchor.href = location.href;\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];\n\n\t\tif ( isFunction( func ) ) {\n\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( ( dataType = dataTypes[ i++ ] ) ) {\n\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[ 0 ] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t( structure[ dataType ] = structure[ dataType ] || [] ).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif ( typeof dataTypeOrTransport === \"string\" &&\n\t\t\t\t!seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t} );\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes trac-9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile ( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader( \"Content-Type\" );\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[ 0 ] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s.throws ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tstate: \"parsererror\",\n\t\t\t\t\t\t\t\terror: conv ? e : \"No conversion from \" + prev + \" to \" + current\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n\njQuery.extend( {\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: location.href,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( location.protocol ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /\\bxml\\b/,\n\t\t\thtml: /\\bhtml/,\n\t\t\tjson: /\\bjson\\b/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": JSON.parse,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar transport,\n\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\n\t\t\t// Url cleanup var\n\t\t\turlAnchor,\n\n\t\t\t// Request state (becomes false upon send and true upon completion)\n\t\t\tcompleted,\n\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\n\t\t\t// Loop variable\n\t\t\ti,\n\n\t\t\t// uncached part of the url\n\t\t\tuncached,\n\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context &&\n\t\t\t\t( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks( \"once memory\" ),\n\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( completed ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( ( match = rheaders.exec( responseHeadersString ) ) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[ 1 ].toLowerCase() + \" \" ] =\n\t\t\t\t\t\t\t\t\t( responseHeaders[ match[ 1 ].toLowerCase() + \" \" ] || [] )\n\t\t\t\t\t\t\t\t\t\t.concat( match[ 2 ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() + \" \" ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match.join( \", \" );\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn completed ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tif ( completed == null ) {\n\t\t\t\t\t\tname = requestHeadersNames[ name.toLowerCase() ] =\n\t\t\t\t\t\t\trequestHeadersNames[ name.toLowerCase() ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( completed == null ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( completed ) {\n\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Lazy-add the new callbacks in a way that preserves old ones\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR );\n\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (trac-10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || location.href ) + \"\" )\n\t\t\t.replace( rprotocol, location.protocol + \"//\" );\n\n\t\t// Alias method option to type as per ticket trac-12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = ( s.dataType || \"*\" ).toLowerCase().match( rnothtmlwhite ) || [ \"\" ];\n\n\t\t// A cross-domain request is in order when the origin doesn't match the current origin.\n\t\tif ( s.crossDomain == null ) {\n\t\t\turlAnchor = document.createElement( \"a\" );\n\n\t\t\t// Support: IE <=8 - 11, Edge 12 - 15\n\t\t\t// IE throws exception on accessing the href property if url is malformed,\n\t\t\t// e.g. http://example.com:80x/\n\t\t\ttry {\n\t\t\t\turlAnchor.href = s.url;\n\n\t\t\t\t// Support: IE <=8 - 11 only\n\t\t\t\t// Anchor's host property isn't correctly set when s.url is relative\n\t\t\t\turlAnchor.href = urlAnchor.href;\n\t\t\t\ts.crossDomain = originAnchor.protocol + \"//\" + originAnchor.host !==\n\t\t\t\t\turlAnchor.protocol + \"//\" + urlAnchor.host;\n\t\t\t} catch ( e ) {\n\n\t\t\t\t// If there is an error parsing the URL, assume it is crossDomain,\n\t\t\t\t// it can be rejected by the transport if it is invalid\n\t\t\t\ts.crossDomain = true;\n\t\t\t}\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( completed ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\t// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118)\n\t\tfireGlobals = jQuery.event && s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger( \"ajaxStart\" );\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\t// Remove hash to simplify url manipulation\n\t\tcacheURL = s.url.replace( rhash, \"\" );\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// Remember the hash so we can put it back\n\t\t\tuncached = s.url.slice( cacheURL.length );\n\n\t\t\t// If data is available and should be processed, append data to url\n\t\t\tif ( s.data && ( s.processData || typeof s.data === \"string\" ) ) {\n\t\t\t\tcacheURL += ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data;\n\n\t\t\t\t// trac-9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add or update anti-cache param if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\tcacheURL = cacheURL.replace( rantiCache, \"$1\" );\n\t\t\t\tuncached = ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + ( nonce.guid++ ) +\n\t\t\t\t\tuncached;\n\t\t\t}\n\n\t\t\t// Put hash and anti-cache on the URL that will be requested (gh-1732)\n\t\t\ts.url = cacheURL + uncached;\n\n\t\t// Change '%20' to '+' if this is encoded form body content (gh-2658)\n\t\t} else if ( s.data && s.processData &&\n\t\t\t( s.contentType || \"\" ).indexOf( \"application/x-www-form-urlencoded\" ) === 0 ) {\n\t\t\ts.data = s.data.replace( r20, \"+\" );\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[ 0 ] ] +\n\t\t\t\t\t( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend &&\n\t\t\t( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {\n\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// Aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tcompleteDeferred.add( s.complete );\n\t\tjqXHR.done( s.success );\n\t\tjqXHR.fail( s.error );\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\n\t\t\t// If request was aborted inside ajaxSend, stop there\n\t\t\tif ( completed ) {\n\t\t\t\treturn jqXHR;\n\t\t\t}\n\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = window.setTimeout( function() {\n\t\t\t\t\tjqXHR.abort( \"timeout\" );\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tcompleted = false;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\n\t\t\t\t// Rethrow post-completion exceptions\n\t\t\t\tif ( completed ) {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\n\t\t\t\t// Propagate others as results\n\t\t\t\tdone( -1, e );\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Ignore repeat invocations\n\t\t\tif ( completed ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcompleted = true;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\twindow.clearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Use a noop converter for missing script but not if jsonp\n\t\t\tif ( !isSuccess &&\n\t\t\t\tjQuery.inArray( \"script\", s.dataTypes ) > -1 &&\n\t\t\t\tjQuery.inArray( \"json\", s.dataTypes ) < 0 ) {\n\t\t\t\ts.converters[ \"text script\" ] = function() {};\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader( \"Last-Modified\" );\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader( \"etag\" );\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// Extract error from statusText and normalize for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger( \"ajaxStop\" );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n} );\n\njQuery.each( [ \"get\", \"post\" ], function( _i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\n\t\t// Shift arguments if data argument was omitted\n\t\tif ( isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\t// The url can be an options object (which then must have .url)\n\t\treturn jQuery.ajax( jQuery.extend( {\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t}, jQuery.isPlainObject( url ) && url ) );\n\t};\n} );\n\njQuery.ajaxPrefilter( function( s ) {\n\tvar i;\n\tfor ( i in s.headers ) {\n\t\tif ( i.toLowerCase() === \"content-type\" ) {\n\t\t\ts.contentType = s.headers[ i ] || \"\";\n\t\t}\n\t}\n} );\n\n\njQuery._evalUrl = function( url, options, doc ) {\n\treturn jQuery.ajax( {\n\t\turl: url,\n\n\t\t// Make this explicit, since user can override this through ajaxSetup (trac-11264)\n\t\ttype: \"GET\",\n\t\tdataType: \"script\",\n\t\tcache: true,\n\t\tasync: false,\n\t\tglobal: false,\n\n\t\t// Only evaluate the response if it is successful (gh-4126)\n\t\t// dataFilter is not invoked for failure responses, so using it instead\n\t\t// of the default converter is kludgy but it works.\n\t\tconverters: {\n\t\t\t\"text script\": function() {}\n\t\t},\n\t\tdataFilter: function( response ) {\n\t\t\tjQuery.globalEval( response, options, doc );\n\t\t}\n\t} );\n};\n\n\njQuery.fn.extend( {\n\twrapAll: function( html ) {\n\t\tvar wrap;\n\n\t\tif ( this[ 0 ] ) {\n\t\t\tif ( isFunction( html ) ) {\n\t\t\t\thtml = html.call( this[ 0 ] );\n\t\t\t}\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );\n\n\t\t\tif ( this[ 0 ].parentNode ) {\n\t\t\t\twrap.insertBefore( this[ 0 ] );\n\t\t\t}\n\n\t\t\twrap.map( function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstElementChild ) {\n\t\t\t\t\telem = elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t} ).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( isFunction( html ) ) {\n\t\t\treturn this.each( function( i ) {\n\t\t\t\tjQuery( this ).wrapInner( html.call( this, i ) );\n\t\t\t} );\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t} );\n\t},\n\n\twrap: function( html ) {\n\t\tvar htmlIsFunction = isFunction( html );\n\n\t\treturn this.each( function( i ) {\n\t\t\tjQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );\n\t\t} );\n\t},\n\n\tunwrap: function( selector ) {\n\t\tthis.parent( selector ).not( \"body\" ).each( function() {\n\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t} );\n\t\treturn this;\n\t}\n} );\n\n\njQuery.expr.pseudos.hidden = function( elem ) {\n\treturn !jQuery.expr.pseudos.visible( elem );\n};\njQuery.expr.pseudos.visible = function( elem ) {\n\treturn !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );\n};\n\n\n\n\njQuery.ajaxSettings.xhr = function() {\n\ttry {\n\t\treturn new window.XMLHttpRequest();\n\t} catch ( e ) {}\n};\n\nvar xhrSuccessStatus = {\n\n\t\t// File protocol always yields status code 0, assume 200\n\t\t0: 200,\n\n\t\t// Support: IE <=9 only\n\t\t// trac-1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported = jQuery.ajaxSettings.xhr();\n\nsupport.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nsupport.ajax = xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport( function( options ) {\n\tvar callback, errorCallback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif ( support.cors || xhrSupported && !options.crossDomain ) {\n\t\treturn {\n\t\t\tsend: function( headers, complete ) {\n\t\t\t\tvar i,\n\t\t\t\t\txhr = options.xhr();\n\n\t\t\t\txhr.open(\n\t\t\t\t\toptions.type,\n\t\t\t\t\toptions.url,\n\t\t\t\t\toptions.async,\n\t\t\t\t\toptions.username,\n\t\t\t\t\toptions.password\n\t\t\t\t);\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif ( options.xhrFields ) {\n\t\t\t\t\tfor ( i in options.xhrFields ) {\n\t\t\t\t\t\txhr[ i ] = options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif ( options.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\txhr.overrideMimeType( options.mimeType );\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\tif ( !options.crossDomain && !headers[ \"X-Requested-With\" ] ) {\n\t\t\t\t\theaders[ \"X-Requested-With\" ] = \"XMLHttpRequest\";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback = function( type ) {\n\t\t\t\t\treturn function() {\n\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\tcallback = errorCallback = xhr.onload =\n\t\t\t\t\t\t\t\txhr.onerror = xhr.onabort = xhr.ontimeout =\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif ( type === \"abort\" ) {\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t} else if ( type === \"error\" ) {\n\n\t\t\t\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t\t\t\t// On a manual native abort, IE9 throws\n\t\t\t\t\t\t\t\t// errors on any property access that is not readyState\n\t\t\t\t\t\t\t\tif ( typeof xhr.status !== \"number\" ) {\n\t\t\t\t\t\t\t\t\tcomplete( 0, \"error\" );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tcomplete(\n\n\t\t\t\t\t\t\t\t\t\t// File: protocol always yields status 0; see trac-8605, trac-14207\n\t\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ] || xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\n\t\t\t\t\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t\t\t\t\t// IE9 has no XHR2 but throws on binary (trac-11426)\n\t\t\t\t\t\t\t\t\t// For XHR2 non-text, let the caller handle it (gh-2498)\n\t\t\t\t\t\t\t\t\t( xhr.responseType || \"text\" ) !== \"text\"  ||\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText !== \"string\" ?\n\t\t\t\t\t\t\t\t\t\t{ binary: xhr.response } :\n\t\t\t\t\t\t\t\t\t\t{ text: xhr.responseText },\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload = callback();\n\t\t\t\terrorCallback = xhr.onerror = xhr.ontimeout = callback( \"error\" );\n\n\t\t\t\t// Support: IE 9 only\n\t\t\t\t// Use onreadystatechange to replace onabort\n\t\t\t\t// to handle uncaught aborts\n\t\t\t\tif ( xhr.onabort !== undefined ) {\n\t\t\t\t\txhr.onabort = errorCallback;\n\t\t\t\t} else {\n\t\t\t\t\txhr.onreadystatechange = function() {\n\n\t\t\t\t\t\t// Check readyState before timeout as it changes\n\t\t\t\t\t\tif ( xhr.readyState === 4 ) {\n\n\t\t\t\t\t\t\t// Allow onerror to be called first,\n\t\t\t\t\t\t\t// but that will not handle a native abort\n\t\t\t\t\t\t\t// Also, save errorCallback to a variable\n\t\t\t\t\t\t\t// as xhr.onerror cannot be accessed\n\t\t\t\t\t\t\twindow.setTimeout( function() {\n\t\t\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\t\t\terrorCallback();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback = callback( \"abort\" );\n\n\t\t\t\ttry {\n\n\t\t\t\t\t// Do send the request (this may raise an exception)\n\t\t\t\t\txhr.send( options.hasContent && options.data || null );\n\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t// trac-14683: Only rethrow if this hasn't been notified as an error yet\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tthrow e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n} );\n\n\n\n\n// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)\njQuery.ajaxPrefilter( function( s ) {\n\tif ( s.crossDomain ) {\n\t\ts.contents.script = false;\n\t}\n} );\n\n// Install script dataType\njQuery.ajaxSetup( {\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, \" +\n\t\t\t\"application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /\\b(?:java|ecma)script\\b/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n} );\n\n// Handle cache's special case and crossDomain\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t}\n} );\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function( s ) {\n\n\t// This transport only deals with cross domain or forced-by-attrs requests\n\tif ( s.crossDomain || s.scriptAttrs ) {\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function( _, complete ) {\n\t\t\t\tscript = jQuery( \"<script>\" )\n\t\t\t\t\t.attr( s.scriptAttrs || {} )\n\t\t\t\t\t.prop( { charset: s.scriptCharset, src: s.url } )\n\t\t\t\t\t.on( \"load error\", callback = function( evt ) {\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback = null;\n\t\t\t\t\t\tif ( evt ) {\n\t\t\t\t\t\t\tcomplete( evt.type === \"error\" ? 404 : 200, evt.type );\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\n\t\t\t\t// Use native DOM manipulation to avoid our domManip AJAX trickery\n\t\t\t\tdocument.head.appendChild( script[ 0 ] );\n\t\t\t},\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n} );\n\n\n\n\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup( {\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce.guid++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n} );\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" &&\n\t\t\t\t( s.contentType || \"\" )\n\t\t\t\t\t.indexOf( \"application/x-www-form-urlencoded\" ) === 0 &&\n\t\t\t\trjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[ \"script json\" ] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// Force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always( function() {\n\n\t\t\t// If previous value didn't exist - remove it\n\t\t\tif ( overwritten === undefined ) {\n\t\t\t\tjQuery( window ).removeProp( callbackName );\n\n\t\t\t// Otherwise restore preexisting value\n\t\t\t} else {\n\t\t\t\twindow[ callbackName ] = overwritten;\n\t\t\t}\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\n\t\t\t\t// Make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// Save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t} );\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n} );\n\n\n\n\n// Support: Safari 8 only\n// In Safari 8 documents created via document.implementation.createHTMLDocument\n// collapse sibling forms: the second one becomes a child of the first one.\n// Because of that, this security measure has to be disabled in Safari 8.\n// https://bugs.webkit.org/show_bug.cgi?id=137337\nsupport.createHTMLDocument = ( function() {\n\tvar body = document.implementation.createHTMLDocument( \"\" ).body;\n\tbody.innerHTML = \"<form></form><form></form>\";\n\treturn body.childNodes.length === 2;\n} )();\n\n\n// Argument \"data\" should be string of html\n// context (optional): If specified, the fragment will be created in this context,\n// defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML = function( data, context, keepScripts ) {\n\tif ( typeof data !== \"string\" ) {\n\t\treturn [];\n\t}\n\tif ( typeof context === \"boolean\" ) {\n\t\tkeepScripts = context;\n\t\tcontext = false;\n\t}\n\n\tvar base, parsed, scripts;\n\n\tif ( !context ) {\n\n\t\t// Stop scripts or inline event handlers from being executed immediately\n\t\t// by using document.implementation\n\t\tif ( support.createHTMLDocument ) {\n\t\t\tcontext = document.implementation.createHTMLDocument( \"\" );\n\n\t\t\t// Set the base href for the created document\n\t\t\t// so any parsed elements with URLs\n\t\t\t// are based on the document's URL (gh-2965)\n\t\t\tbase = context.createElement( \"base\" );\n\t\t\tbase.href = document.location.href;\n\t\t\tcontext.head.appendChild( base );\n\t\t} else {\n\t\t\tcontext = document;\n\t\t}\n\t}\n\n\tparsed = rsingleTag.exec( data );\n\tscripts = !keepScripts && [];\n\n\t// Single tag\n\tif ( parsed ) {\n\t\treturn [ context.createElement( parsed[ 1 ] ) ];\n\t}\n\n\tparsed = buildFragment( [ data ], context, scripts );\n\n\tif ( scripts && scripts.length ) {\n\t\tjQuery( scripts ).remove();\n\t}\n\n\treturn jQuery.merge( [], parsed.childNodes );\n};\n\n\n/**\n * Load a url into a page\n */\njQuery.fn.load = function( url, params, callback ) {\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf( \" \" );\n\n\tif ( off > -1 ) {\n\t\tselector = stripAndCollapse( url.slice( off ) );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax( {\n\t\t\turl: url,\n\n\t\t\t// If \"type\" variable is undefined, then \"GET\" method will be used.\n\t\t\t// Make value of this field explicit since\n\t\t\t// user can override it through ajaxSetup method\n\t\t\ttype: type || \"GET\",\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t} ).done( function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery( \"<div>\" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t// If the request succeeds, this function gets \"data\", \"status\", \"jqXHR\"\n\t\t// but they are ignored because response was set above.\n\t\t// If it fails, this function gets \"jqXHR\", \"status\", \"error\"\n\t\t} ).always( callback && function( jqXHR, status ) {\n\t\t\tself.each( function() {\n\t\t\t\tcallback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t\t} );\n\t\t} );\n\t}\n\n\treturn this;\n};\n\n\n\n\njQuery.expr.pseudos.animated = function( elem ) {\n\treturn jQuery.grep( jQuery.timers, function( fn ) {\n\t\treturn elem === fn.elem;\n\t} ).length;\n};\n\n\n\n\njQuery.offset = {\n\tsetOffset: function( elem, options, i ) {\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition = jQuery.css( elem, \"position\" ),\n\t\t\tcurElem = jQuery( elem ),\n\t\t\tprops = {};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tcurOffset = curElem.offset();\n\t\tcurCSSTop = jQuery.css( elem, \"top\" );\n\t\tcurCSSLeft = jQuery.css( elem, \"left\" );\n\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n\t\t\t( curCSSTop + curCSSLeft ).indexOf( \"auto\" ) > -1;\n\n\t\t// Need to be able to calculate position if either\n\t\t// top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( isFunction( options ) ) {\n\n\t\t\t// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)\n\t\t\toptions = options.call( elem, i, jQuery.extend( {}, curOffset ) );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\njQuery.fn.extend( {\n\n\t// offset() relates an element's border box to the document origin\n\toffset: function( options ) {\n\n\t\t// Preserve chaining for setter\n\t\tif ( arguments.length ) {\n\t\t\treturn options === undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each( function( i ) {\n\t\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t\t} );\n\t\t}\n\n\t\tvar rect, win,\n\t\t\telem = this[ 0 ];\n\n\t\tif ( !elem ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Return zeros for disconnected and hidden (display: none) elements (gh-2310)\n\t\t// Support: IE <=11 only\n\t\t// Running getBoundingClientRect on a\n\t\t// disconnected node in IE throws an error\n\t\tif ( !elem.getClientRects().length ) {\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t\t// Get document-relative position by adding viewport scroll to viewport-relative gBCR\n\t\trect = elem.getBoundingClientRect();\n\t\twin = elem.ownerDocument.defaultView;\n\t\treturn {\n\t\t\ttop: rect.top + win.pageYOffset,\n\t\t\tleft: rect.left + win.pageXOffset\n\t\t};\n\t},\n\n\t// position() relates an element's margin box to its offset parent's padding box\n\t// This corresponds to the behavior of CSS absolute positioning\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset, doc,\n\t\t\telem = this[ 0 ],\n\t\t\tparentOffset = { top: 0, left: 0 };\n\n\t\t// position:fixed elements are offset from the viewport, which itself always has zero offset\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\n\t\t\t// Assume position:fixed implies availability of getBoundingClientRect\n\t\t\toffset = elem.getBoundingClientRect();\n\n\t\t} else {\n\t\t\toffset = this.offset();\n\n\t\t\t// Account for the *real* offset parent, which can be the document or its root element\n\t\t\t// when a statically positioned element is identified\n\t\t\tdoc = elem.ownerDocument;\n\t\t\toffsetParent = elem.offsetParent || doc.documentElement;\n\t\t\twhile ( offsetParent &&\n\t\t\t\t( offsetParent === doc.body || offsetParent === doc.documentElement ) &&\n\t\t\t\tjQuery.css( offsetParent, \"position\" ) === \"static\" ) {\n\n\t\t\t\toffsetParent = offsetParent.parentNode;\n\t\t\t}\n\t\t\tif ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {\n\n\t\t\t\t// Incorporate borders into its offset, since they are outside its content origin\n\t\t\t\tparentOffset = jQuery( offsetParent ).offset();\n\t\t\t\tparentOffset.top += jQuery.css( offsetParent, \"borderTopWidth\", true );\n\t\t\t\tparentOffset.left += jQuery.css( offsetParent, \"borderLeftWidth\", true );\n\t\t\t}\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true )\n\t\t};\n\t},\n\n\t// This method will return documentElement in the following cases:\n\t// 1) For the element inside the iframe without offsetParent, this method will return\n\t//    documentElement of the parent window\n\t// 2) For the hidden or detached element\n\t// 3) For body or html element, i.e. in case of the html node - it will return itself\n\t//\n\t// but those exceptions were never presented as a real life use-cases\n\t// and might be considered as more preferable results.\n\t//\n\t// This logic, however, is not guaranteed and can change at any point in the future\n\toffsetParent: function() {\n\t\treturn this.map( function() {\n\t\t\tvar offsetParent = this.offsetParent;\n\n\t\t\twhile ( offsetParent && jQuery.css( offsetParent, \"position\" ) === \"static\" ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent || documentElement;\n\t\t} );\n\t}\n} );\n\n// Create scrollLeft and scrollTop methods\njQuery.each( { scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function( method, prop ) {\n\tvar top = \"pageYOffset\" === prop;\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn access( this, function( elem, method, val ) {\n\n\t\t\t// Coalesce documents and windows\n\t\t\tvar win;\n\t\t\tif ( isWindow( elem ) ) {\n\t\t\t\twin = elem;\n\t\t\t} else if ( elem.nodeType === 9 ) {\n\t\t\t\twin = elem.defaultView;\n\t\t\t}\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? win[ prop ] : elem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : win.pageXOffset,\n\t\t\t\t\ttop ? val : win.pageYOffset\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length );\n\t};\n} );\n\n// Support: Safari <=7 - 9.1, Chrome <=37 - 49\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347\n// getComputedStyle returns percent when specified for top/left/bottom/right;\n// rather than make the css module depend on the offset module, just check for it here\njQuery.each( [ \"top\", \"left\" ], function( _i, prop ) {\n\tjQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,\n\t\tfunction( elem, computed ) {\n\t\t\tif ( computed ) {\n\t\t\t\tcomputed = curCSS( elem, prop );\n\n\t\t\t\t// If curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n} );\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( {\n\t\tpadding: \"inner\" + name,\n\t\tcontent: type,\n\t\t\"\": \"outer\" + name\n\t}, function( defaultExtra, funcName ) {\n\n\t\t// Margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( isWindow( elem ) ) {\n\n\t\t\t\t\t// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)\n\t\t\t\t\treturn funcName.indexOf( \"outer\" ) === 0 ?\n\t\t\t\t\t\telem[ \"inner\" + name ] :\n\t\t\t\t\t\telem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable );\n\t\t};\n\t} );\n} );\n\n\njQuery.each( [\n\t\"ajaxStart\",\n\t\"ajaxStop\",\n\t\"ajaxComplete\",\n\t\"ajaxError\",\n\t\"ajaxSuccess\",\n\t\"ajaxSend\"\n], function( _i, type ) {\n\tjQuery.fn[ type ] = function( fn ) {\n\t\treturn this.on( type, fn );\n\t};\n} );\n\n\n\n\njQuery.fn.extend( {\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ?\n\t\t\tthis.off( selector, \"**\" ) :\n\t\t\tthis.off( types, selector || \"**\", fn );\n\t},\n\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t}\n} );\n\njQuery.each(\n\t( \"blur focus focusin focusout resize scroll click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup contextmenu\" ).split( \" \" ),\n\tfunction( _i, name ) {\n\n\t\t// Handle event binding\n\t\tjQuery.fn[ name ] = function( data, fn ) {\n\t\t\treturn arguments.length > 0 ?\n\t\t\t\tthis.on( name, null, data, fn ) :\n\t\t\t\tthis.trigger( name );\n\t\t};\n\t}\n);\n\n\n\n\n// Support: Android <=4.0 only\n// Make sure we trim BOM and NBSP\n// Require that the \"whitespace run\" starts from a non-whitespace\n// to avoid O(N^2) behavior when the engine would try matching \"\\s+$\" at each space position.\nvar rtrim = /^[\\s\\uFEFF\\xA0]+|([^\\s\\uFEFF\\xA0])[\\s\\uFEFF\\xA0]+$/g;\n\n// Bind a function to a context, optionally partially applying any\n// arguments.\n// jQuery.proxy is deprecated to promote standards (specifically Function#bind)\n// However, it is not slated for removal any time soon\njQuery.proxy = function( fn, context ) {\n\tvar tmp, args, proxy;\n\n\tif ( typeof context === \"string\" ) {\n\t\ttmp = fn[ context ];\n\t\tcontext = fn;\n\t\tfn = tmp;\n\t}\n\n\t// Quick check to determine if target is callable, in the spec\n\t// this throws a TypeError, but we will just return undefined.\n\tif ( !isFunction( fn ) ) {\n\t\treturn undefined;\n\t}\n\n\t// Simulated bind\n\targs = slice.call( arguments, 2 );\n\tproxy = function() {\n\t\treturn fn.apply( context || this, args.concat( slice.call( arguments ) ) );\n\t};\n\n\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\treturn proxy;\n};\n\njQuery.holdReady = function( hold ) {\n\tif ( hold ) {\n\t\tjQuery.readyWait++;\n\t} else {\n\t\tjQuery.ready( true );\n\t}\n};\njQuery.isArray = Array.isArray;\njQuery.parseJSON = JSON.parse;\njQuery.nodeName = nodeName;\njQuery.isFunction = isFunction;\njQuery.isWindow = isWindow;\njQuery.camelCase = camelCase;\njQuery.type = toType;\n\njQuery.now = Date.now;\n\njQuery.isNumeric = function( obj ) {\n\n\t// As of jQuery 3.0, isNumeric is limited to\n\t// strings and numbers (primitives or objects)\n\t// that can be coerced to finite numbers (gh-2662)\n\tvar type = jQuery.type( obj );\n\treturn ( type === \"number\" || type === \"string\" ) &&\n\n\t\t// parseFloat NaNs numeric-cast false positives (\"\")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n\t\t// subtraction forces infinities to NaN\n\t\t!isNaN( obj - parseFloat( obj ) );\n};\n\njQuery.trim = function( text ) {\n\treturn text == null ?\n\t\t\"\" :\n\t\t( text + \"\" ).replace( rtrim, \"$1\" );\n};\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\n\n// Note that for maximum portability, libraries that are not jQuery should\n// declare themselves as anonymous modules, and avoid setting a global if an\n// AMD loader is present. jQuery is a special case. For more information, see\n// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon\n\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( \"jquery\", [], function() {\n\t\treturn jQuery;\n\t} );\n}\n\n\n\n\nvar\n\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$;\n\njQuery.noConflict = function( deep ) {\n\tif ( window.$ === jQuery ) {\n\t\twindow.$ = _$;\n\t}\n\n\tif ( deep && window.jQuery === jQuery ) {\n\t\twindow.jQuery = _jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in AMD\n// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (trac-13566)\nif ( typeof noGlobal === \"undefined\" ) {\n\twindow.jQuery = window.$ = jQuery;\n}\n\n\n\n\nreturn jQuery;\n} );", "/**\n * @popperjs/core v2.11.8 - MIT License\n */\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Popper = {}));\n}(this, (function (exports) { 'use strict';\n\n  function getWindow(node) {\n    if (node == null) {\n      return window;\n    }\n\n    if (node.toString() !== '[object Window]') {\n      var ownerDocument = node.ownerDocument;\n      return ownerDocument ? ownerDocument.defaultView || window : window;\n    }\n\n    return node;\n  }\n\n  function isElement(node) {\n    var OwnElement = getWindow(node).Element;\n    return node instanceof OwnElement || node instanceof Element;\n  }\n\n  function isHTMLElement(node) {\n    var OwnElement = getWindow(node).HTMLElement;\n    return node instanceof OwnElement || node instanceof HTMLElement;\n  }\n\n  function isShadowRoot(node) {\n    // IE 11 has no ShadowRoot\n    if (typeof ShadowRoot === 'undefined') {\n      return false;\n    }\n\n    var OwnElement = getWindow(node).ShadowRoot;\n    return node instanceof OwnElement || node instanceof ShadowRoot;\n  }\n\n  var max = Math.max;\n  var min = Math.min;\n  var round = Math.round;\n\n  function getUAString() {\n    var uaData = navigator.userAgentData;\n\n    if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n      return uaData.brands.map(function (item) {\n        return item.brand + \"/\" + item.version;\n      }).join(' ');\n    }\n\n    return navigator.userAgent;\n  }\n\n  function isLayoutViewport() {\n    return !/^((?!chrome|android).)*safari/i.test(getUAString());\n  }\n\n  function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n    if (includeScale === void 0) {\n      includeScale = false;\n    }\n\n    if (isFixedStrategy === void 0) {\n      isFixedStrategy = false;\n    }\n\n    var clientRect = element.getBoundingClientRect();\n    var scaleX = 1;\n    var scaleY = 1;\n\n    if (includeScale && isHTMLElement(element)) {\n      scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n      scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n    }\n\n    var _ref = isElement(element) ? getWindow(element) : window,\n        visualViewport = _ref.visualViewport;\n\n    var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n    var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n    var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n    var width = clientRect.width / scaleX;\n    var height = clientRect.height / scaleY;\n    return {\n      width: width,\n      height: height,\n      top: y,\n      right: x + width,\n      bottom: y + height,\n      left: x,\n      x: x,\n      y: y\n    };\n  }\n\n  function getWindowScroll(node) {\n    var win = getWindow(node);\n    var scrollLeft = win.pageXOffset;\n    var scrollTop = win.pageYOffset;\n    return {\n      scrollLeft: scrollLeft,\n      scrollTop: scrollTop\n    };\n  }\n\n  function getHTMLElementScroll(element) {\n    return {\n      scrollLeft: element.scrollLeft,\n      scrollTop: element.scrollTop\n    };\n  }\n\n  function getNodeScroll(node) {\n    if (node === getWindow(node) || !isHTMLElement(node)) {\n      return getWindowScroll(node);\n    } else {\n      return getHTMLElementScroll(node);\n    }\n  }\n\n  function getNodeName(element) {\n    return element ? (element.nodeName || '').toLowerCase() : null;\n  }\n\n  function getDocumentElement(element) {\n    // $FlowFixMe[incompatible-return]: assume body is always available\n    return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n    element.document) || window.document).documentElement;\n  }\n\n  function getWindowScrollBarX(element) {\n    // If <html> has a CSS width greater than the viewport, then this will be\n    // incorrect for RTL.\n    // Popper 1 is broken in this case and never had a bug report so let's assume\n    // it's not an issue. I don't think anyone ever specifies width on <html>\n    // anyway.\n    // Browsers where the left scrollbar doesn't cause an issue report `0` for\n    // this (e.g. Edge 2019, IE11, Safari)\n    return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n  }\n\n  function getComputedStyle(element) {\n    return getWindow(element).getComputedStyle(element);\n  }\n\n  function isScrollParent(element) {\n    // Firefox wants us to check `-x` and `-y` variations as well\n    var _getComputedStyle = getComputedStyle(element),\n        overflow = _getComputedStyle.overflow,\n        overflowX = _getComputedStyle.overflowX,\n        overflowY = _getComputedStyle.overflowY;\n\n    return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n  }\n\n  function isElementScaled(element) {\n    var rect = element.getBoundingClientRect();\n    var scaleX = round(rect.width) / element.offsetWidth || 1;\n    var scaleY = round(rect.height) / element.offsetHeight || 1;\n    return scaleX !== 1 || scaleY !== 1;\n  } // Returns the composite rect of an element relative to its offsetParent.\n  // Composite means it takes into account transforms as well as layout.\n\n\n  function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n    if (isFixed === void 0) {\n      isFixed = false;\n    }\n\n    var isOffsetParentAnElement = isHTMLElement(offsetParent);\n    var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n    var documentElement = getDocumentElement(offsetParent);\n    var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n    var scroll = {\n      scrollLeft: 0,\n      scrollTop: 0\n    };\n    var offsets = {\n      x: 0,\n      y: 0\n    };\n\n    if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n      if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n      isScrollParent(documentElement)) {\n        scroll = getNodeScroll(offsetParent);\n      }\n\n      if (isHTMLElement(offsetParent)) {\n        offsets = getBoundingClientRect(offsetParent, true);\n        offsets.x += offsetParent.clientLeft;\n        offsets.y += offsetParent.clientTop;\n      } else if (documentElement) {\n        offsets.x = getWindowScrollBarX(documentElement);\n      }\n    }\n\n    return {\n      x: rect.left + scroll.scrollLeft - offsets.x,\n      y: rect.top + scroll.scrollTop - offsets.y,\n      width: rect.width,\n      height: rect.height\n    };\n  }\n\n  // means it doesn't take into account transforms.\n\n  function getLayoutRect(element) {\n    var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n    // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n    var width = element.offsetWidth;\n    var height = element.offsetHeight;\n\n    if (Math.abs(clientRect.width - width) <= 1) {\n      width = clientRect.width;\n    }\n\n    if (Math.abs(clientRect.height - height) <= 1) {\n      height = clientRect.height;\n    }\n\n    return {\n      x: element.offsetLeft,\n      y: element.offsetTop,\n      width: width,\n      height: height\n    };\n  }\n\n  function getParentNode(element) {\n    if (getNodeName(element) === 'html') {\n      return element;\n    }\n\n    return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n      // $FlowFixMe[incompatible-return]\n      // $FlowFixMe[prop-missing]\n      element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n      element.parentNode || ( // DOM Element detected\n      isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n      // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n      getDocumentElement(element) // fallback\n\n    );\n  }\n\n  function getScrollParent(node) {\n    if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n      // $FlowFixMe[incompatible-return]: assume body is always available\n      return node.ownerDocument.body;\n    }\n\n    if (isHTMLElement(node) && isScrollParent(node)) {\n      return node;\n    }\n\n    return getScrollParent(getParentNode(node));\n  }\n\n  /*\n  given a DOM element, return the list of all scroll parents, up the list of ancesors\n  until we get to the top window object. This list is what we attach scroll listeners\n  to, because if any of these parent elements scroll, we'll need to re-calculate the\n  reference element's position.\n  */\n\n  function listScrollParents(element, list) {\n    var _element$ownerDocumen;\n\n    if (list === void 0) {\n      list = [];\n    }\n\n    var scrollParent = getScrollParent(element);\n    var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n    var win = getWindow(scrollParent);\n    var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n    var updatedList = list.concat(target);\n    return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n    updatedList.concat(listScrollParents(getParentNode(target)));\n  }\n\n  function isTableElement(element) {\n    return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n  }\n\n  function getTrueOffsetParent(element) {\n    if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n    getComputedStyle(element).position === 'fixed') {\n      return null;\n    }\n\n    return element.offsetParent;\n  } // `.offsetParent` reports `null` for fixed elements, while absolute elements\n  // return the containing block\n\n\n  function getContainingBlock(element) {\n    var isFirefox = /firefox/i.test(getUAString());\n    var isIE = /Trident/i.test(getUAString());\n\n    if (isIE && isHTMLElement(element)) {\n      // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n      var elementCss = getComputedStyle(element);\n\n      if (elementCss.position === 'fixed') {\n        return null;\n      }\n    }\n\n    var currentNode = getParentNode(element);\n\n    if (isShadowRoot(currentNode)) {\n      currentNode = currentNode.host;\n    }\n\n    while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n      var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n      // create a containing block.\n      // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n      if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n        return currentNode;\n      } else {\n        currentNode = currentNode.parentNode;\n      }\n    }\n\n    return null;\n  } // Gets the closest ancestor positioned element. Handles some edge cases,\n  // such as table ancestors and cross browser bugs.\n\n\n  function getOffsetParent(element) {\n    var window = getWindow(element);\n    var offsetParent = getTrueOffsetParent(element);\n\n    while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n      offsetParent = getTrueOffsetParent(offsetParent);\n    }\n\n    if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n      return window;\n    }\n\n    return offsetParent || getContainingBlock(element) || window;\n  }\n\n  var top = 'top';\n  var bottom = 'bottom';\n  var right = 'right';\n  var left = 'left';\n  var auto = 'auto';\n  var basePlacements = [top, bottom, right, left];\n  var start = 'start';\n  var end = 'end';\n  var clippingParents = 'clippingParents';\n  var viewport = 'viewport';\n  var popper = 'popper';\n  var reference = 'reference';\n  var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n    return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n  }, []);\n  var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n    return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n  }, []); // modifiers that need to read the DOM\n\n  var beforeRead = 'beforeRead';\n  var read = 'read';\n  var afterRead = 'afterRead'; // pure-logic modifiers\n\n  var beforeMain = 'beforeMain';\n  var main = 'main';\n  var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\n  var beforeWrite = 'beforeWrite';\n  var write = 'write';\n  var afterWrite = 'afterWrite';\n  var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];\n\n  function order(modifiers) {\n    var map = new Map();\n    var visited = new Set();\n    var result = [];\n    modifiers.forEach(function (modifier) {\n      map.set(modifier.name, modifier);\n    }); // On visiting object, check for its dependencies and visit them recursively\n\n    function sort(modifier) {\n      visited.add(modifier.name);\n      var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n      requires.forEach(function (dep) {\n        if (!visited.has(dep)) {\n          var depModifier = map.get(dep);\n\n          if (depModifier) {\n            sort(depModifier);\n          }\n        }\n      });\n      result.push(modifier);\n    }\n\n    modifiers.forEach(function (modifier) {\n      if (!visited.has(modifier.name)) {\n        // check for visited object\n        sort(modifier);\n      }\n    });\n    return result;\n  }\n\n  function orderModifiers(modifiers) {\n    // order based on dependencies\n    var orderedModifiers = order(modifiers); // order based on phase\n\n    return modifierPhases.reduce(function (acc, phase) {\n      return acc.concat(orderedModifiers.filter(function (modifier) {\n        return modifier.phase === phase;\n      }));\n    }, []);\n  }\n\n  function debounce(fn) {\n    var pending;\n    return function () {\n      if (!pending) {\n        pending = new Promise(function (resolve) {\n          Promise.resolve().then(function () {\n            pending = undefined;\n            resolve(fn());\n          });\n        });\n      }\n\n      return pending;\n    };\n  }\n\n  function mergeByName(modifiers) {\n    var merged = modifiers.reduce(function (merged, current) {\n      var existing = merged[current.name];\n      merged[current.name] = existing ? Object.assign({}, existing, current, {\n        options: Object.assign({}, existing.options, current.options),\n        data: Object.assign({}, existing.data, current.data)\n      }) : current;\n      return merged;\n    }, {}); // IE11 does not support Object.values\n\n    return Object.keys(merged).map(function (key) {\n      return merged[key];\n    });\n  }\n\n  function getViewportRect(element, strategy) {\n    var win = getWindow(element);\n    var html = getDocumentElement(element);\n    var visualViewport = win.visualViewport;\n    var width = html.clientWidth;\n    var height = html.clientHeight;\n    var x = 0;\n    var y = 0;\n\n    if (visualViewport) {\n      width = visualViewport.width;\n      height = visualViewport.height;\n      var layoutViewport = isLayoutViewport();\n\n      if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n        x = visualViewport.offsetLeft;\n        y = visualViewport.offsetTop;\n      }\n    }\n\n    return {\n      width: width,\n      height: height,\n      x: x + getWindowScrollBarX(element),\n      y: y\n    };\n  }\n\n  // of the `<html>` and `<body>` rect bounds if horizontally scrollable\n\n  function getDocumentRect(element) {\n    var _element$ownerDocumen;\n\n    var html = getDocumentElement(element);\n    var winScroll = getWindowScroll(element);\n    var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n    var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n    var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n    var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n    var y = -winScroll.scrollTop;\n\n    if (getComputedStyle(body || html).direction === 'rtl') {\n      x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n    }\n\n    return {\n      width: width,\n      height: height,\n      x: x,\n      y: y\n    };\n  }\n\n  function contains(parent, child) {\n    var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n    if (parent.contains(child)) {\n      return true;\n    } // then fallback to custom implementation with Shadow DOM support\n    else if (rootNode && isShadowRoot(rootNode)) {\n        var next = child;\n\n        do {\n          if (next && parent.isSameNode(next)) {\n            return true;\n          } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n          next = next.parentNode || next.host;\n        } while (next);\n      } // Give up, the result is false\n\n\n    return false;\n  }\n\n  function rectToClientRect(rect) {\n    return Object.assign({}, rect, {\n      left: rect.x,\n      top: rect.y,\n      right: rect.x + rect.width,\n      bottom: rect.y + rect.height\n    });\n  }\n\n  function getInnerBoundingClientRect(element, strategy) {\n    var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n    rect.top = rect.top + element.clientTop;\n    rect.left = rect.left + element.clientLeft;\n    rect.bottom = rect.top + element.clientHeight;\n    rect.right = rect.left + element.clientWidth;\n    rect.width = element.clientWidth;\n    rect.height = element.clientHeight;\n    rect.x = rect.left;\n    rect.y = rect.top;\n    return rect;\n  }\n\n  function getClientRectFromMixedType(element, clippingParent, strategy) {\n    return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n  } // A \"clipping parent\" is an overflowable container with the characteristic of\n  // clipping (or hiding) overflowing elements with a position different from\n  // `initial`\n\n\n  function getClippingParents(element) {\n    var clippingParents = listScrollParents(getParentNode(element));\n    var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n    var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n    if (!isElement(clipperElement)) {\n      return [];\n    } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n    return clippingParents.filter(function (clippingParent) {\n      return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n    });\n  } // Gets the maximum area that the element is visible in due to any number of\n  // clipping parents\n\n\n  function getClippingRect(element, boundary, rootBoundary, strategy) {\n    var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n    var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n    var firstClippingParent = clippingParents[0];\n    var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n      var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n      accRect.top = max(rect.top, accRect.top);\n      accRect.right = min(rect.right, accRect.right);\n      accRect.bottom = min(rect.bottom, accRect.bottom);\n      accRect.left = max(rect.left, accRect.left);\n      return accRect;\n    }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n    clippingRect.width = clippingRect.right - clippingRect.left;\n    clippingRect.height = clippingRect.bottom - clippingRect.top;\n    clippingRect.x = clippingRect.left;\n    clippingRect.y = clippingRect.top;\n    return clippingRect;\n  }\n\n  function getBasePlacement(placement) {\n    return placement.split('-')[0];\n  }\n\n  function getVariation(placement) {\n    return placement.split('-')[1];\n  }\n\n  function getMainAxisFromPlacement(placement) {\n    return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n  }\n\n  function computeOffsets(_ref) {\n    var reference = _ref.reference,\n        element = _ref.element,\n        placement = _ref.placement;\n    var basePlacement = placement ? getBasePlacement(placement) : null;\n    var variation = placement ? getVariation(placement) : null;\n    var commonX = reference.x + reference.width / 2 - element.width / 2;\n    var commonY = reference.y + reference.height / 2 - element.height / 2;\n    var offsets;\n\n    switch (basePlacement) {\n      case top:\n        offsets = {\n          x: commonX,\n          y: reference.y - element.height\n        };\n        break;\n\n      case bottom:\n        offsets = {\n          x: commonX,\n          y: reference.y + reference.height\n        };\n        break;\n\n      case right:\n        offsets = {\n          x: reference.x + reference.width,\n          y: commonY\n        };\n        break;\n\n      case left:\n        offsets = {\n          x: reference.x - element.width,\n          y: commonY\n        };\n        break;\n\n      default:\n        offsets = {\n          x: reference.x,\n          y: reference.y\n        };\n    }\n\n    var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n    if (mainAxis != null) {\n      var len = mainAxis === 'y' ? 'height' : 'width';\n\n      switch (variation) {\n        case start:\n          offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n          break;\n\n        case end:\n          offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n          break;\n      }\n    }\n\n    return offsets;\n  }\n\n  function getFreshSideObject() {\n    return {\n      top: 0,\n      right: 0,\n      bottom: 0,\n      left: 0\n    };\n  }\n\n  function mergePaddingObject(paddingObject) {\n    return Object.assign({}, getFreshSideObject(), paddingObject);\n  }\n\n  function expandToHashMap(value, keys) {\n    return keys.reduce(function (hashMap, key) {\n      hashMap[key] = value;\n      return hashMap;\n    }, {});\n  }\n\n  function detectOverflow(state, options) {\n    if (options === void 0) {\n      options = {};\n    }\n\n    var _options = options,\n        _options$placement = _options.placement,\n        placement = _options$placement === void 0 ? state.placement : _options$placement,\n        _options$strategy = _options.strategy,\n        strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n        _options$boundary = _options.boundary,\n        boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n        _options$rootBoundary = _options.rootBoundary,\n        rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n        _options$elementConte = _options.elementContext,\n        elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n        _options$altBoundary = _options.altBoundary,\n        altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n        _options$padding = _options.padding,\n        padding = _options$padding === void 0 ? 0 : _options$padding;\n    var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n    var altContext = elementContext === popper ? reference : popper;\n    var popperRect = state.rects.popper;\n    var element = state.elements[altBoundary ? altContext : elementContext];\n    var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n    var referenceClientRect = getBoundingClientRect(state.elements.reference);\n    var popperOffsets = computeOffsets({\n      reference: referenceClientRect,\n      element: popperRect,\n      strategy: 'absolute',\n      placement: placement\n    });\n    var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n    var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n    // 0 or negative = within the clipping rect\n\n    var overflowOffsets = {\n      top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n      bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n      left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n      right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n    };\n    var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n    if (elementContext === popper && offsetData) {\n      var offset = offsetData[placement];\n      Object.keys(overflowOffsets).forEach(function (key) {\n        var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n        var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n        overflowOffsets[key] += offset[axis] * multiply;\n      });\n    }\n\n    return overflowOffsets;\n  }\n\n  var DEFAULT_OPTIONS = {\n    placement: 'bottom',\n    modifiers: [],\n    strategy: 'absolute'\n  };\n\n  function areValidElements() {\n    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n      args[_key] = arguments[_key];\n    }\n\n    return !args.some(function (element) {\n      return !(element && typeof element.getBoundingClientRect === 'function');\n    });\n  }\n\n  function popperGenerator(generatorOptions) {\n    if (generatorOptions === void 0) {\n      generatorOptions = {};\n    }\n\n    var _generatorOptions = generatorOptions,\n        _generatorOptions$def = _generatorOptions.defaultModifiers,\n        defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n        _generatorOptions$def2 = _generatorOptions.defaultOptions,\n        defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n    return function createPopper(reference, popper, options) {\n      if (options === void 0) {\n        options = defaultOptions;\n      }\n\n      var state = {\n        placement: 'bottom',\n        orderedModifiers: [],\n        options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n        modifiersData: {},\n        elements: {\n          reference: reference,\n          popper: popper\n        },\n        attributes: {},\n        styles: {}\n      };\n      var effectCleanupFns = [];\n      var isDestroyed = false;\n      var instance = {\n        state: state,\n        setOptions: function setOptions(setOptionsAction) {\n          var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n          cleanupModifierEffects();\n          state.options = Object.assign({}, defaultOptions, state.options, options);\n          state.scrollParents = {\n            reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n            popper: listScrollParents(popper)\n          }; // Orders the modifiers based on their dependencies and `phase`\n          // properties\n\n          var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n          state.orderedModifiers = orderedModifiers.filter(function (m) {\n            return m.enabled;\n          });\n          runModifierEffects();\n          return instance.update();\n        },\n        // Sync update \u2013 it will always be executed, even if not necessary. This\n        // is useful for low frequency updates where sync behavior simplifies the\n        // logic.\n        // For high frequency updates (e.g. `resize` and `scroll` events), always\n        // prefer the async Popper#update method\n        forceUpdate: function forceUpdate() {\n          if (isDestroyed) {\n            return;\n          }\n\n          var _state$elements = state.elements,\n              reference = _state$elements.reference,\n              popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n          // anymore\n\n          if (!areValidElements(reference, popper)) {\n            return;\n          } // Store the reference and popper rects to be read by modifiers\n\n\n          state.rects = {\n            reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n            popper: getLayoutRect(popper)\n          }; // Modifiers have the ability to reset the current update cycle. The\n          // most common use case for this is the `flip` modifier changing the\n          // placement, which then needs to re-run all the modifiers, because the\n          // logic was previously ran for the previous placement and is therefore\n          // stale/incorrect\n\n          state.reset = false;\n          state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n          // is filled with the initial data specified by the modifier. This means\n          // it doesn't persist and is fresh on each update.\n          // To ensure persistent data, use `${name}#persistent`\n\n          state.orderedModifiers.forEach(function (modifier) {\n            return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n          });\n\n          for (var index = 0; index < state.orderedModifiers.length; index++) {\n            if (state.reset === true) {\n              state.reset = false;\n              index = -1;\n              continue;\n            }\n\n            var _state$orderedModifie = state.orderedModifiers[index],\n                fn = _state$orderedModifie.fn,\n                _state$orderedModifie2 = _state$orderedModifie.options,\n                _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n                name = _state$orderedModifie.name;\n\n            if (typeof fn === 'function') {\n              state = fn({\n                state: state,\n                options: _options,\n                name: name,\n                instance: instance\n              }) || state;\n            }\n          }\n        },\n        // Async and optimistically optimized update \u2013 it will not be executed if\n        // not necessary (debounced to run at most once-per-tick)\n        update: debounce(function () {\n          return new Promise(function (resolve) {\n            instance.forceUpdate();\n            resolve(state);\n          });\n        }),\n        destroy: function destroy() {\n          cleanupModifierEffects();\n          isDestroyed = true;\n        }\n      };\n\n      if (!areValidElements(reference, popper)) {\n        return instance;\n      }\n\n      instance.setOptions(options).then(function (state) {\n        if (!isDestroyed && options.onFirstUpdate) {\n          options.onFirstUpdate(state);\n        }\n      }); // Modifiers have the ability to execute arbitrary code before the first\n      // update cycle runs. They will be executed in the same order as the update\n      // cycle. This is useful when a modifier adds some persistent data that\n      // other modifiers need to use, but the modifier is run after the dependent\n      // one.\n\n      function runModifierEffects() {\n        state.orderedModifiers.forEach(function (_ref) {\n          var name = _ref.name,\n              _ref$options = _ref.options,\n              options = _ref$options === void 0 ? {} : _ref$options,\n              effect = _ref.effect;\n\n          if (typeof effect === 'function') {\n            var cleanupFn = effect({\n              state: state,\n              name: name,\n              instance: instance,\n              options: options\n            });\n\n            var noopFn = function noopFn() {};\n\n            effectCleanupFns.push(cleanupFn || noopFn);\n          }\n        });\n      }\n\n      function cleanupModifierEffects() {\n        effectCleanupFns.forEach(function (fn) {\n          return fn();\n        });\n        effectCleanupFns = [];\n      }\n\n      return instance;\n    };\n  }\n\n  var passive = {\n    passive: true\n  };\n\n  function effect$2(_ref) {\n    var state = _ref.state,\n        instance = _ref.instance,\n        options = _ref.options;\n    var _options$scroll = options.scroll,\n        scroll = _options$scroll === void 0 ? true : _options$scroll,\n        _options$resize = options.resize,\n        resize = _options$resize === void 0 ? true : _options$resize;\n    var window = getWindow(state.elements.popper);\n    var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n    if (scroll) {\n      scrollParents.forEach(function (scrollParent) {\n        scrollParent.addEventListener('scroll', instance.update, passive);\n      });\n    }\n\n    if (resize) {\n      window.addEventListener('resize', instance.update, passive);\n    }\n\n    return function () {\n      if (scroll) {\n        scrollParents.forEach(function (scrollParent) {\n          scrollParent.removeEventListener('scroll', instance.update, passive);\n        });\n      }\n\n      if (resize) {\n        window.removeEventListener('resize', instance.update, passive);\n      }\n    };\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var eventListeners = {\n    name: 'eventListeners',\n    enabled: true,\n    phase: 'write',\n    fn: function fn() {},\n    effect: effect$2,\n    data: {}\n  };\n\n  function popperOffsets(_ref) {\n    var state = _ref.state,\n        name = _ref.name;\n    // Offsets are the actual position the popper needs to have to be\n    // properly positioned near its reference element\n    // This is the most basic placement, and will be adjusted by\n    // the modifiers in the next step\n    state.modifiersData[name] = computeOffsets({\n      reference: state.rects.reference,\n      element: state.rects.popper,\n      strategy: 'absolute',\n      placement: state.placement\n    });\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var popperOffsets$1 = {\n    name: 'popperOffsets',\n    enabled: true,\n    phase: 'read',\n    fn: popperOffsets,\n    data: {}\n  };\n\n  var unsetSides = {\n    top: 'auto',\n    right: 'auto',\n    bottom: 'auto',\n    left: 'auto'\n  }; // Round the offsets to the nearest suitable subpixel based on the DPR.\n  // Zooming can change the DPR, but it seems to report a value that will\n  // cleanly divide the values into the appropriate subpixels.\n\n  function roundOffsetsByDPR(_ref, win) {\n    var x = _ref.x,\n        y = _ref.y;\n    var dpr = win.devicePixelRatio || 1;\n    return {\n      x: round(x * dpr) / dpr || 0,\n      y: round(y * dpr) / dpr || 0\n    };\n  }\n\n  function mapToStyles(_ref2) {\n    var _Object$assign2;\n\n    var popper = _ref2.popper,\n        popperRect = _ref2.popperRect,\n        placement = _ref2.placement,\n        variation = _ref2.variation,\n        offsets = _ref2.offsets,\n        position = _ref2.position,\n        gpuAcceleration = _ref2.gpuAcceleration,\n        adaptive = _ref2.adaptive,\n        roundOffsets = _ref2.roundOffsets,\n        isFixed = _ref2.isFixed;\n    var _offsets$x = offsets.x,\n        x = _offsets$x === void 0 ? 0 : _offsets$x,\n        _offsets$y = offsets.y,\n        y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n    var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n      x: x,\n      y: y\n    }) : {\n      x: x,\n      y: y\n    };\n\n    x = _ref3.x;\n    y = _ref3.y;\n    var hasX = offsets.hasOwnProperty('x');\n    var hasY = offsets.hasOwnProperty('y');\n    var sideX = left;\n    var sideY = top;\n    var win = window;\n\n    if (adaptive) {\n      var offsetParent = getOffsetParent(popper);\n      var heightProp = 'clientHeight';\n      var widthProp = 'clientWidth';\n\n      if (offsetParent === getWindow(popper)) {\n        offsetParent = getDocumentElement(popper);\n\n        if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n          heightProp = 'scrollHeight';\n          widthProp = 'scrollWidth';\n        }\n      } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n      offsetParent = offsetParent;\n\n      if (placement === top || (placement === left || placement === right) && variation === end) {\n        sideY = bottom;\n        var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n        offsetParent[heightProp];\n        y -= offsetY - popperRect.height;\n        y *= gpuAcceleration ? 1 : -1;\n      }\n\n      if (placement === left || (placement === top || placement === bottom) && variation === end) {\n        sideX = right;\n        var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n        offsetParent[widthProp];\n        x -= offsetX - popperRect.width;\n        x *= gpuAcceleration ? 1 : -1;\n      }\n    }\n\n    var commonStyles = Object.assign({\n      position: position\n    }, adaptive && unsetSides);\n\n    var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n      x: x,\n      y: y\n    }, getWindow(popper)) : {\n      x: x,\n      y: y\n    };\n\n    x = _ref4.x;\n    y = _ref4.y;\n\n    if (gpuAcceleration) {\n      var _Object$assign;\n\n      return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n    }\n\n    return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n  }\n\n  function computeStyles(_ref5) {\n    var state = _ref5.state,\n        options = _ref5.options;\n    var _options$gpuAccelerat = options.gpuAcceleration,\n        gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n        _options$adaptive = options.adaptive,\n        adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n        _options$roundOffsets = options.roundOffsets,\n        roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n    var commonStyles = {\n      placement: getBasePlacement(state.placement),\n      variation: getVariation(state.placement),\n      popper: state.elements.popper,\n      popperRect: state.rects.popper,\n      gpuAcceleration: gpuAcceleration,\n      isFixed: state.options.strategy === 'fixed'\n    };\n\n    if (state.modifiersData.popperOffsets != null) {\n      state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n        offsets: state.modifiersData.popperOffsets,\n        position: state.options.strategy,\n        adaptive: adaptive,\n        roundOffsets: roundOffsets\n      })));\n    }\n\n    if (state.modifiersData.arrow != null) {\n      state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n        offsets: state.modifiersData.arrow,\n        position: 'absolute',\n        adaptive: false,\n        roundOffsets: roundOffsets\n      })));\n    }\n\n    state.attributes.popper = Object.assign({}, state.attributes.popper, {\n      'data-popper-placement': state.placement\n    });\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var computeStyles$1 = {\n    name: 'computeStyles',\n    enabled: true,\n    phase: 'beforeWrite',\n    fn: computeStyles,\n    data: {}\n  };\n\n  // and applies them to the HTMLElements such as popper and arrow\n\n  function applyStyles(_ref) {\n    var state = _ref.state;\n    Object.keys(state.elements).forEach(function (name) {\n      var style = state.styles[name] || {};\n      var attributes = state.attributes[name] || {};\n      var element = state.elements[name]; // arrow is optional + virtual elements\n\n      if (!isHTMLElement(element) || !getNodeName(element)) {\n        return;\n      } // Flow doesn't support to extend this property, but it's the most\n      // effective way to apply styles to an HTMLElement\n      // $FlowFixMe[cannot-write]\n\n\n      Object.assign(element.style, style);\n      Object.keys(attributes).forEach(function (name) {\n        var value = attributes[name];\n\n        if (value === false) {\n          element.removeAttribute(name);\n        } else {\n          element.setAttribute(name, value === true ? '' : value);\n        }\n      });\n    });\n  }\n\n  function effect$1(_ref2) {\n    var state = _ref2.state;\n    var initialStyles = {\n      popper: {\n        position: state.options.strategy,\n        left: '0',\n        top: '0',\n        margin: '0'\n      },\n      arrow: {\n        position: 'absolute'\n      },\n      reference: {}\n    };\n    Object.assign(state.elements.popper.style, initialStyles.popper);\n    state.styles = initialStyles;\n\n    if (state.elements.arrow) {\n      Object.assign(state.elements.arrow.style, initialStyles.arrow);\n    }\n\n    return function () {\n      Object.keys(state.elements).forEach(function (name) {\n        var element = state.elements[name];\n        var attributes = state.attributes[name] || {};\n        var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n        var style = styleProperties.reduce(function (style, property) {\n          style[property] = '';\n          return style;\n        }, {}); // arrow is optional + virtual elements\n\n        if (!isHTMLElement(element) || !getNodeName(element)) {\n          return;\n        }\n\n        Object.assign(element.style, style);\n        Object.keys(attributes).forEach(function (attribute) {\n          element.removeAttribute(attribute);\n        });\n      });\n    };\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var applyStyles$1 = {\n    name: 'applyStyles',\n    enabled: true,\n    phase: 'write',\n    fn: applyStyles,\n    effect: effect$1,\n    requires: ['computeStyles']\n  };\n\n  function distanceAndSkiddingToXY(placement, rects, offset) {\n    var basePlacement = getBasePlacement(placement);\n    var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n    var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n      placement: placement\n    })) : offset,\n        skidding = _ref[0],\n        distance = _ref[1];\n\n    skidding = skidding || 0;\n    distance = (distance || 0) * invertDistance;\n    return [left, right].indexOf(basePlacement) >= 0 ? {\n      x: distance,\n      y: skidding\n    } : {\n      x: skidding,\n      y: distance\n    };\n  }\n\n  function offset(_ref2) {\n    var state = _ref2.state,\n        options = _ref2.options,\n        name = _ref2.name;\n    var _options$offset = options.offset,\n        offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n    var data = placements.reduce(function (acc, placement) {\n      acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n      return acc;\n    }, {});\n    var _data$state$placement = data[state.placement],\n        x = _data$state$placement.x,\n        y = _data$state$placement.y;\n\n    if (state.modifiersData.popperOffsets != null) {\n      state.modifiersData.popperOffsets.x += x;\n      state.modifiersData.popperOffsets.y += y;\n    }\n\n    state.modifiersData[name] = data;\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var offset$1 = {\n    name: 'offset',\n    enabled: true,\n    phase: 'main',\n    requires: ['popperOffsets'],\n    fn: offset\n  };\n\n  var hash$1 = {\n    left: 'right',\n    right: 'left',\n    bottom: 'top',\n    top: 'bottom'\n  };\n  function getOppositePlacement(placement) {\n    return placement.replace(/left|right|bottom|top/g, function (matched) {\n      return hash$1[matched];\n    });\n  }\n\n  var hash = {\n    start: 'end',\n    end: 'start'\n  };\n  function getOppositeVariationPlacement(placement) {\n    return placement.replace(/start|end/g, function (matched) {\n      return hash[matched];\n    });\n  }\n\n  function computeAutoPlacement(state, options) {\n    if (options === void 0) {\n      options = {};\n    }\n\n    var _options = options,\n        placement = _options.placement,\n        boundary = _options.boundary,\n        rootBoundary = _options.rootBoundary,\n        padding = _options.padding,\n        flipVariations = _options.flipVariations,\n        _options$allowedAutoP = _options.allowedAutoPlacements,\n        allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP;\n    var variation = getVariation(placement);\n    var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n      return getVariation(placement) === variation;\n    }) : basePlacements;\n    var allowedPlacements = placements$1.filter(function (placement) {\n      return allowedAutoPlacements.indexOf(placement) >= 0;\n    });\n\n    if (allowedPlacements.length === 0) {\n      allowedPlacements = placements$1;\n    } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n    var overflows = allowedPlacements.reduce(function (acc, placement) {\n      acc[placement] = detectOverflow(state, {\n        placement: placement,\n        boundary: boundary,\n        rootBoundary: rootBoundary,\n        padding: padding\n      })[getBasePlacement(placement)];\n      return acc;\n    }, {});\n    return Object.keys(overflows).sort(function (a, b) {\n      return overflows[a] - overflows[b];\n    });\n  }\n\n  function getExpandedFallbackPlacements(placement) {\n    if (getBasePlacement(placement) === auto) {\n      return [];\n    }\n\n    var oppositePlacement = getOppositePlacement(placement);\n    return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n  }\n\n  function flip(_ref) {\n    var state = _ref.state,\n        options = _ref.options,\n        name = _ref.name;\n\n    if (state.modifiersData[name]._skip) {\n      return;\n    }\n\n    var _options$mainAxis = options.mainAxis,\n        checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n        _options$altAxis = options.altAxis,\n        checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n        specifiedFallbackPlacements = options.fallbackPlacements,\n        padding = options.padding,\n        boundary = options.boundary,\n        rootBoundary = options.rootBoundary,\n        altBoundary = options.altBoundary,\n        _options$flipVariatio = options.flipVariations,\n        flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n        allowedAutoPlacements = options.allowedAutoPlacements;\n    var preferredPlacement = state.options.placement;\n    var basePlacement = getBasePlacement(preferredPlacement);\n    var isBasePlacement = basePlacement === preferredPlacement;\n    var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n    var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n      return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n        placement: placement,\n        boundary: boundary,\n        rootBoundary: rootBoundary,\n        padding: padding,\n        flipVariations: flipVariations,\n        allowedAutoPlacements: allowedAutoPlacements\n      }) : placement);\n    }, []);\n    var referenceRect = state.rects.reference;\n    var popperRect = state.rects.popper;\n    var checksMap = new Map();\n    var makeFallbackChecks = true;\n    var firstFittingPlacement = placements[0];\n\n    for (var i = 0; i < placements.length; i++) {\n      var placement = placements[i];\n\n      var _basePlacement = getBasePlacement(placement);\n\n      var isStartVariation = getVariation(placement) === start;\n      var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n      var len = isVertical ? 'width' : 'height';\n      var overflow = detectOverflow(state, {\n        placement: placement,\n        boundary: boundary,\n        rootBoundary: rootBoundary,\n        altBoundary: altBoundary,\n        padding: padding\n      });\n      var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n      if (referenceRect[len] > popperRect[len]) {\n        mainVariationSide = getOppositePlacement(mainVariationSide);\n      }\n\n      var altVariationSide = getOppositePlacement(mainVariationSide);\n      var checks = [];\n\n      if (checkMainAxis) {\n        checks.push(overflow[_basePlacement] <= 0);\n      }\n\n      if (checkAltAxis) {\n        checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n      }\n\n      if (checks.every(function (check) {\n        return check;\n      })) {\n        firstFittingPlacement = placement;\n        makeFallbackChecks = false;\n        break;\n      }\n\n      checksMap.set(placement, checks);\n    }\n\n    if (makeFallbackChecks) {\n      // `2` may be desired in some cases \u2013 research later\n      var numberOfChecks = flipVariations ? 3 : 1;\n\n      var _loop = function _loop(_i) {\n        var fittingPlacement = placements.find(function (placement) {\n          var checks = checksMap.get(placement);\n\n          if (checks) {\n            return checks.slice(0, _i).every(function (check) {\n              return check;\n            });\n          }\n        });\n\n        if (fittingPlacement) {\n          firstFittingPlacement = fittingPlacement;\n          return \"break\";\n        }\n      };\n\n      for (var _i = numberOfChecks; _i > 0; _i--) {\n        var _ret = _loop(_i);\n\n        if (_ret === \"break\") break;\n      }\n    }\n\n    if (state.placement !== firstFittingPlacement) {\n      state.modifiersData[name]._skip = true;\n      state.placement = firstFittingPlacement;\n      state.reset = true;\n    }\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var flip$1 = {\n    name: 'flip',\n    enabled: true,\n    phase: 'main',\n    fn: flip,\n    requiresIfExists: ['offset'],\n    data: {\n      _skip: false\n    }\n  };\n\n  function getAltAxis(axis) {\n    return axis === 'x' ? 'y' : 'x';\n  }\n\n  function within(min$1, value, max$1) {\n    return max(min$1, min(value, max$1));\n  }\n  function withinMaxClamp(min, value, max) {\n    var v = within(min, value, max);\n    return v > max ? max : v;\n  }\n\n  function preventOverflow(_ref) {\n    var state = _ref.state,\n        options = _ref.options,\n        name = _ref.name;\n    var _options$mainAxis = options.mainAxis,\n        checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n        _options$altAxis = options.altAxis,\n        checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n        boundary = options.boundary,\n        rootBoundary = options.rootBoundary,\n        altBoundary = options.altBoundary,\n        padding = options.padding,\n        _options$tether = options.tether,\n        tether = _options$tether === void 0 ? true : _options$tether,\n        _options$tetherOffset = options.tetherOffset,\n        tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n    var overflow = detectOverflow(state, {\n      boundary: boundary,\n      rootBoundary: rootBoundary,\n      padding: padding,\n      altBoundary: altBoundary\n    });\n    var basePlacement = getBasePlacement(state.placement);\n    var variation = getVariation(state.placement);\n    var isBasePlacement = !variation;\n    var mainAxis = getMainAxisFromPlacement(basePlacement);\n    var altAxis = getAltAxis(mainAxis);\n    var popperOffsets = state.modifiersData.popperOffsets;\n    var referenceRect = state.rects.reference;\n    var popperRect = state.rects.popper;\n    var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n      placement: state.placement\n    })) : tetherOffset;\n    var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n      mainAxis: tetherOffsetValue,\n      altAxis: tetherOffsetValue\n    } : Object.assign({\n      mainAxis: 0,\n      altAxis: 0\n    }, tetherOffsetValue);\n    var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n    var data = {\n      x: 0,\n      y: 0\n    };\n\n    if (!popperOffsets) {\n      return;\n    }\n\n    if (checkMainAxis) {\n      var _offsetModifierState$;\n\n      var mainSide = mainAxis === 'y' ? top : left;\n      var altSide = mainAxis === 'y' ? bottom : right;\n      var len = mainAxis === 'y' ? 'height' : 'width';\n      var offset = popperOffsets[mainAxis];\n      var min$1 = offset + overflow[mainSide];\n      var max$1 = offset - overflow[altSide];\n      var additive = tether ? -popperRect[len] / 2 : 0;\n      var minLen = variation === start ? referenceRect[len] : popperRect[len];\n      var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n      // outside the reference bounds\n\n      var arrowElement = state.elements.arrow;\n      var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n        width: 0,\n        height: 0\n      };\n      var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n      var arrowPaddingMin = arrowPaddingObject[mainSide];\n      var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n      // to include its full size in the calculation. If the reference is small\n      // and near the edge of a boundary, the popper can overflow even if the\n      // reference is not overflowing as well (e.g. virtual elements with no\n      // width or height)\n\n      var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n      var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n      var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n      var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n      var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n      var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n      var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n      var tetherMax = offset + maxOffset - offsetModifierValue;\n      var preventedOffset = within(tether ? min(min$1, tetherMin) : min$1, offset, tether ? max(max$1, tetherMax) : max$1);\n      popperOffsets[mainAxis] = preventedOffset;\n      data[mainAxis] = preventedOffset - offset;\n    }\n\n    if (checkAltAxis) {\n      var _offsetModifierState$2;\n\n      var _mainSide = mainAxis === 'x' ? top : left;\n\n      var _altSide = mainAxis === 'x' ? bottom : right;\n\n      var _offset = popperOffsets[altAxis];\n\n      var _len = altAxis === 'y' ? 'height' : 'width';\n\n      var _min = _offset + overflow[_mainSide];\n\n      var _max = _offset - overflow[_altSide];\n\n      var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n      var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n      var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n      var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n      var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n      popperOffsets[altAxis] = _preventedOffset;\n      data[altAxis] = _preventedOffset - _offset;\n    }\n\n    state.modifiersData[name] = data;\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var preventOverflow$1 = {\n    name: 'preventOverflow',\n    enabled: true,\n    phase: 'main',\n    fn: preventOverflow,\n    requiresIfExists: ['offset']\n  };\n\n  var toPaddingObject = function toPaddingObject(padding, state) {\n    padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n      placement: state.placement\n    })) : padding;\n    return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n  };\n\n  function arrow(_ref) {\n    var _state$modifiersData$;\n\n    var state = _ref.state,\n        name = _ref.name,\n        options = _ref.options;\n    var arrowElement = state.elements.arrow;\n    var popperOffsets = state.modifiersData.popperOffsets;\n    var basePlacement = getBasePlacement(state.placement);\n    var axis = getMainAxisFromPlacement(basePlacement);\n    var isVertical = [left, right].indexOf(basePlacement) >= 0;\n    var len = isVertical ? 'height' : 'width';\n\n    if (!arrowElement || !popperOffsets) {\n      return;\n    }\n\n    var paddingObject = toPaddingObject(options.padding, state);\n    var arrowRect = getLayoutRect(arrowElement);\n    var minProp = axis === 'y' ? top : left;\n    var maxProp = axis === 'y' ? bottom : right;\n    var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n    var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n    var arrowOffsetParent = getOffsetParent(arrowElement);\n    var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n    var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n    // outside of the popper bounds\n\n    var min = paddingObject[minProp];\n    var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n    var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n    var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n    var axisProp = axis;\n    state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n  }\n\n  function effect(_ref2) {\n    var state = _ref2.state,\n        options = _ref2.options;\n    var _options$element = options.element,\n        arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n    if (arrowElement == null) {\n      return;\n    } // CSS selector\n\n\n    if (typeof arrowElement === 'string') {\n      arrowElement = state.elements.popper.querySelector(arrowElement);\n\n      if (!arrowElement) {\n        return;\n      }\n    }\n\n    if (!contains(state.elements.popper, arrowElement)) {\n      return;\n    }\n\n    state.elements.arrow = arrowElement;\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var arrow$1 = {\n    name: 'arrow',\n    enabled: true,\n    phase: 'main',\n    fn: arrow,\n    effect: effect,\n    requires: ['popperOffsets'],\n    requiresIfExists: ['preventOverflow']\n  };\n\n  function getSideOffsets(overflow, rect, preventedOffsets) {\n    if (preventedOffsets === void 0) {\n      preventedOffsets = {\n        x: 0,\n        y: 0\n      };\n    }\n\n    return {\n      top: overflow.top - rect.height - preventedOffsets.y,\n      right: overflow.right - rect.width + preventedOffsets.x,\n      bottom: overflow.bottom - rect.height + preventedOffsets.y,\n      left: overflow.left - rect.width - preventedOffsets.x\n    };\n  }\n\n  function isAnySideFullyClipped(overflow) {\n    return [top, right, bottom, left].some(function (side) {\n      return overflow[side] >= 0;\n    });\n  }\n\n  function hide(_ref) {\n    var state = _ref.state,\n        name = _ref.name;\n    var referenceRect = state.rects.reference;\n    var popperRect = state.rects.popper;\n    var preventedOffsets = state.modifiersData.preventOverflow;\n    var referenceOverflow = detectOverflow(state, {\n      elementContext: 'reference'\n    });\n    var popperAltOverflow = detectOverflow(state, {\n      altBoundary: true\n    });\n    var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n    var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n    var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n    var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n    state.modifiersData[name] = {\n      referenceClippingOffsets: referenceClippingOffsets,\n      popperEscapeOffsets: popperEscapeOffsets,\n      isReferenceHidden: isReferenceHidden,\n      hasPopperEscaped: hasPopperEscaped\n    };\n    state.attributes.popper = Object.assign({}, state.attributes.popper, {\n      'data-popper-reference-hidden': isReferenceHidden,\n      'data-popper-escaped': hasPopperEscaped\n    });\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  var hide$1 = {\n    name: 'hide',\n    enabled: true,\n    phase: 'main',\n    requiresIfExists: ['preventOverflow'],\n    fn: hide\n  };\n\n  var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1];\n  var createPopper$1 = /*#__PURE__*/popperGenerator({\n    defaultModifiers: defaultModifiers$1\n  }); // eslint-disable-next-line import/no-unused-modules\n\n  var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1];\n  var createPopper = /*#__PURE__*/popperGenerator({\n    defaultModifiers: defaultModifiers\n  }); // eslint-disable-next-line import/no-unused-modules\n\n  exports.applyStyles = applyStyles$1;\n  exports.arrow = arrow$1;\n  exports.computeStyles = computeStyles$1;\n  exports.createPopper = createPopper;\n  exports.createPopperLite = createPopper$1;\n  exports.defaultModifiers = defaultModifiers;\n  exports.detectOverflow = detectOverflow;\n  exports.eventListeners = eventListeners;\n  exports.flip = flip$1;\n  exports.hide = hide$1;\n  exports.offset = offset$1;\n  exports.popperGenerator = popperGenerator;\n  exports.popperOffsets = popperOffsets$1;\n  exports.preventOverflow = preventOverflow$1;\n\n  Object.defineProperty(exports, '__esModule', { value: true });\n\n})));\n//# sourceMappingURL=popper.js.map\n", "/*!\n  * Bootstrap index.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Index = {}));\n})(this, (function (exports) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/index.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  const MAX_UID = 1000000;\n  const MILLISECONDS_MULTIPLIER = 1000;\n  const TRANSITION_END = 'transitionend';\n\n  /**\n   * Properly escape IDs selectors to handle weird IDs\n   * @param {string} selector\n   * @returns {string}\n   */\n  const parseSelector = selector => {\n    if (selector && window.CSS && window.CSS.escape) {\n      // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n      selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`);\n    }\n    return selector;\n  };\n\n  // Shout-out Angus Croll (https://goo.gl/pxwQGp)\n  const toType = object => {\n    if (object === null || object === undefined) {\n      return `${object}`;\n    }\n    return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n  };\n\n  /**\n   * Public Util API\n   */\n\n  const getUID = prefix => {\n    do {\n      prefix += Math.floor(Math.random() * MAX_UID);\n    } while (document.getElementById(prefix));\n    return prefix;\n  };\n  const getTransitionDurationFromElement = element => {\n    if (!element) {\n      return 0;\n    }\n\n    // Get transition-duration of the element\n    let {\n      transitionDuration,\n      transitionDelay\n    } = window.getComputedStyle(element);\n    const floatTransitionDuration = Number.parseFloat(transitionDuration);\n    const floatTransitionDelay = Number.parseFloat(transitionDelay);\n\n    // Return 0 if element or transition duration is not found\n    if (!floatTransitionDuration && !floatTransitionDelay) {\n      return 0;\n    }\n\n    // If multiple durations are defined, take the first\n    transitionDuration = transitionDuration.split(',')[0];\n    transitionDelay = transitionDelay.split(',')[0];\n    return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n  };\n  const triggerTransitionEnd = element => {\n    element.dispatchEvent(new Event(TRANSITION_END));\n  };\n  const isElement = object => {\n    if (!object || typeof object !== 'object') {\n      return false;\n    }\n    if (typeof object.jquery !== 'undefined') {\n      object = object[0];\n    }\n    return typeof object.nodeType !== 'undefined';\n  };\n  const getElement = object => {\n    // it's a jQuery object or a node element\n    if (isElement(object)) {\n      return object.jquery ? object[0] : object;\n    }\n    if (typeof object === 'string' && object.length > 0) {\n      return document.querySelector(parseSelector(object));\n    }\n    return null;\n  };\n  const isVisible = element => {\n    if (!isElement(element) || element.getClientRects().length === 0) {\n      return false;\n    }\n    const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n    // Handle `details` element as its content may falsie appear visible when it is closed\n    const closedDetails = element.closest('details:not([open])');\n    if (!closedDetails) {\n      return elementIsVisible;\n    }\n    if (closedDetails !== element) {\n      const summary = element.closest('summary');\n      if (summary && summary.parentNode !== closedDetails) {\n        return false;\n      }\n      if (summary === null) {\n        return false;\n      }\n    }\n    return elementIsVisible;\n  };\n  const isDisabled = element => {\n    if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n      return true;\n    }\n    if (element.classList.contains('disabled')) {\n      return true;\n    }\n    if (typeof element.disabled !== 'undefined') {\n      return element.disabled;\n    }\n    return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n  };\n  const findShadowRoot = element => {\n    if (!document.documentElement.attachShadow) {\n      return null;\n    }\n\n    // Can find the shadow root otherwise it'll return the document\n    if (typeof element.getRootNode === 'function') {\n      const root = element.getRootNode();\n      return root instanceof ShadowRoot ? root : null;\n    }\n    if (element instanceof ShadowRoot) {\n      return element;\n    }\n\n    // when we don't find a shadow root\n    if (!element.parentNode) {\n      return null;\n    }\n    return findShadowRoot(element.parentNode);\n  };\n  const noop = () => {};\n\n  /**\n   * Trick to restart an element's animation\n   *\n   * @param {HTMLElement} element\n   * @return void\n   *\n   * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n   */\n  const reflow = element => {\n    element.offsetHeight; // eslint-disable-line no-unused-expressions\n  };\n  const getjQuery = () => {\n    if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n      return window.jQuery;\n    }\n    return null;\n  };\n  const DOMContentLoadedCallbacks = [];\n  const onDOMContentLoaded = callback => {\n    if (document.readyState === 'loading') {\n      // add listener on the first call when the document is in loading state\n      if (!DOMContentLoadedCallbacks.length) {\n        document.addEventListener('DOMContentLoaded', () => {\n          for (const callback of DOMContentLoadedCallbacks) {\n            callback();\n          }\n        });\n      }\n      DOMContentLoadedCallbacks.push(callback);\n    } else {\n      callback();\n    }\n  };\n  const isRTL = () => document.documentElement.dir === 'rtl';\n  const defineJQueryPlugin = plugin => {\n    onDOMContentLoaded(() => {\n      const $ = getjQuery();\n      /* istanbul ignore if */\n      if ($) {\n        const name = plugin.NAME;\n        const JQUERY_NO_CONFLICT = $.fn[name];\n        $.fn[name] = plugin.jQueryInterface;\n        $.fn[name].Constructor = plugin;\n        $.fn[name].noConflict = () => {\n          $.fn[name] = JQUERY_NO_CONFLICT;\n          return plugin.jQueryInterface;\n        };\n      }\n    });\n  };\n  const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n    return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;\n  };\n  const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n    if (!waitForTransition) {\n      execute(callback);\n      return;\n    }\n    const durationPadding = 5;\n    const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n    let called = false;\n    const handler = ({\n      target\n    }) => {\n      if (target !== transitionElement) {\n        return;\n      }\n      called = true;\n      transitionElement.removeEventListener(TRANSITION_END, handler);\n      execute(callback);\n    };\n    transitionElement.addEventListener(TRANSITION_END, handler);\n    setTimeout(() => {\n      if (!called) {\n        triggerTransitionEnd(transitionElement);\n      }\n    }, emulatedDuration);\n  };\n\n  /**\n   * Return the previous/next element of a list.\n   *\n   * @param {array} list    The list of elements\n   * @param activeElement   The active element\n   * @param shouldGetNext   Choose to get next or previous element\n   * @param isCycleAllowed\n   * @return {Element|elem} The proper element\n   */\n  const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n    const listLength = list.length;\n    let index = list.indexOf(activeElement);\n\n    // if the element does not exist in the list return an element\n    // depending on the direction and if cycle is allowed\n    if (index === -1) {\n      return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n    }\n    index += shouldGetNext ? 1 : -1;\n    if (isCycleAllowed) {\n      index = (index + listLength) % listLength;\n    }\n    return list[Math.max(0, Math.min(index, listLength - 1))];\n  };\n\n  exports.defineJQueryPlugin = defineJQueryPlugin;\n  exports.execute = execute;\n  exports.executeAfterTransition = executeAfterTransition;\n  exports.findShadowRoot = findShadowRoot;\n  exports.getElement = getElement;\n  exports.getNextActiveElement = getNextActiveElement;\n  exports.getTransitionDurationFromElement = getTransitionDurationFromElement;\n  exports.getUID = getUID;\n  exports.getjQuery = getjQuery;\n  exports.isDisabled = isDisabled;\n  exports.isElement = isElement;\n  exports.isRTL = isRTL;\n  exports.isVisible = isVisible;\n  exports.noop = noop;\n  exports.onDOMContentLoaded = onDOMContentLoaded;\n  exports.parseSelector = parseSelector;\n  exports.reflow = reflow;\n  exports.toType = toType;\n  exports.triggerTransitionEnd = triggerTransitionEnd;\n\n  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\n}));\n//# sourceMappingURL=index.js.map\n", "/*!\n  * Bootstrap data.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Data = factory());\n})(this, (function () { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap dom/data.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  /**\n   * Constants\n   */\n\n  const elementMap = new Map();\n  const data = {\n    set(element, key, instance) {\n      if (!elementMap.has(element)) {\n        elementMap.set(element, new Map());\n      }\n      const instanceMap = elementMap.get(element);\n\n      // make it clear we only want one instance per element\n      // can be removed later when multiple key/instances are fine to be used\n      if (!instanceMap.has(key) && instanceMap.size !== 0) {\n        // eslint-disable-next-line no-console\n        console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n        return;\n      }\n      instanceMap.set(key, instance);\n    },\n    get(element, key) {\n      if (elementMap.has(element)) {\n        return elementMap.get(element).get(key) || null;\n      }\n      return null;\n    },\n    remove(element, key) {\n      if (!elementMap.has(element)) {\n        return;\n      }\n      const instanceMap = elementMap.get(element);\n      instanceMap.delete(key);\n\n      // free up element references if there are no instances left for an element\n      if (instanceMap.size === 0) {\n        elementMap.delete(element);\n      }\n    }\n  };\n\n  return data;\n\n}));\n//# sourceMappingURL=data.js.map\n", "/*!\n  * Bootstrap event-handler.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['../util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EventHandler = factory(global.Index));\n})(this, (function (index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap dom/event-handler.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\n  const stripNameRegex = /\\..*/;\n  const stripUidRegex = /::\\d+$/;\n  const eventRegistry = {}; // Events storage\n  let uidEvent = 1;\n  const customEvents = {\n    mouseenter: 'mouseover',\n    mouseleave: 'mouseout'\n  };\n  const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n\n  /**\n   * Private methods\n   */\n\n  function makeEventUid(element, uid) {\n    return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n  }\n  function getElementEvents(element) {\n    const uid = makeEventUid(element);\n    element.uidEvent = uid;\n    eventRegistry[uid] = eventRegistry[uid] || {};\n    return eventRegistry[uid];\n  }\n  function bootstrapHandler(element, fn) {\n    return function handler(event) {\n      hydrateObj(event, {\n        delegateTarget: element\n      });\n      if (handler.oneOff) {\n        EventHandler.off(element, event.type, fn);\n      }\n      return fn.apply(element, [event]);\n    };\n  }\n  function bootstrapDelegationHandler(element, selector, fn) {\n    return function handler(event) {\n      const domElements = element.querySelectorAll(selector);\n      for (let {\n        target\n      } = event; target && target !== this; target = target.parentNode) {\n        for (const domElement of domElements) {\n          if (domElement !== target) {\n            continue;\n          }\n          hydrateObj(event, {\n            delegateTarget: target\n          });\n          if (handler.oneOff) {\n            EventHandler.off(element, event.type, selector, fn);\n          }\n          return fn.apply(target, [event]);\n        }\n      }\n    };\n  }\n  function findHandler(events, callable, delegationSelector = null) {\n    return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n  }\n  function normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n    const isDelegated = typeof handler === 'string';\n    // TODO: tooltip passes `false` instead of selector, so we need to check\n    const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n    let typeEvent = getTypeEvent(originalTypeEvent);\n    if (!nativeEvents.has(typeEvent)) {\n      typeEvent = originalTypeEvent;\n    }\n    return [isDelegated, callable, typeEvent];\n  }\n  function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n    if (typeof originalTypeEvent !== 'string' || !element) {\n      return;\n    }\n    let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n\n    // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n    // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n    if (originalTypeEvent in customEvents) {\n      const wrapFunction = fn => {\n        return function (event) {\n          if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n            return fn.call(this, event);\n          }\n        };\n      };\n      callable = wrapFunction(callable);\n    }\n    const events = getElementEvents(element);\n    const handlers = events[typeEvent] || (events[typeEvent] = {});\n    const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n    if (previousFunction) {\n      previousFunction.oneOff = previousFunction.oneOff && oneOff;\n      return;\n    }\n    const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n    const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n    fn.delegationSelector = isDelegated ? handler : null;\n    fn.callable = callable;\n    fn.oneOff = oneOff;\n    fn.uidEvent = uid;\n    handlers[uid] = fn;\n    element.addEventListener(typeEvent, fn, isDelegated);\n  }\n  function removeHandler(element, events, typeEvent, handler, delegationSelector) {\n    const fn = findHandler(events[typeEvent], handler, delegationSelector);\n    if (!fn) {\n      return;\n    }\n    element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n    delete events[typeEvent][fn.uidEvent];\n  }\n  function removeNamespacedHandlers(element, events, typeEvent, namespace) {\n    const storeElementEvent = events[typeEvent] || {};\n    for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n      if (handlerKey.includes(namespace)) {\n        removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n      }\n    }\n  }\n  function getTypeEvent(event) {\n    // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n    event = event.replace(stripNameRegex, '');\n    return customEvents[event] || event;\n  }\n  const EventHandler = {\n    on(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, false);\n    },\n    one(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, true);\n    },\n    off(element, originalTypeEvent, handler, delegationFunction) {\n      if (typeof originalTypeEvent !== 'string' || !element) {\n        return;\n      }\n      const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n      const inNamespace = typeEvent !== originalTypeEvent;\n      const events = getElementEvents(element);\n      const storeElementEvent = events[typeEvent] || {};\n      const isNamespace = originalTypeEvent.startsWith('.');\n      if (typeof callable !== 'undefined') {\n        // Simplest case: handler is passed, remove that listener ONLY.\n        if (!Object.keys(storeElementEvent).length) {\n          return;\n        }\n        removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n        return;\n      }\n      if (isNamespace) {\n        for (const elementEvent of Object.keys(events)) {\n          removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n        }\n      }\n      for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n        const handlerKey = keyHandlers.replace(stripUidRegex, '');\n        if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n          removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n        }\n      }\n    },\n    trigger(element, event, args) {\n      if (typeof event !== 'string' || !element) {\n        return null;\n      }\n      const $ = index_js.getjQuery();\n      const typeEvent = getTypeEvent(event);\n      const inNamespace = event !== typeEvent;\n      let jQueryEvent = null;\n      let bubbles = true;\n      let nativeDispatch = true;\n      let defaultPrevented = false;\n      if (inNamespace && $) {\n        jQueryEvent = $.Event(event, args);\n        $(element).trigger(jQueryEvent);\n        bubbles = !jQueryEvent.isPropagationStopped();\n        nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n        defaultPrevented = jQueryEvent.isDefaultPrevented();\n      }\n      const evt = hydrateObj(new Event(event, {\n        bubbles,\n        cancelable: true\n      }), args);\n      if (defaultPrevented) {\n        evt.preventDefault();\n      }\n      if (nativeDispatch) {\n        element.dispatchEvent(evt);\n      }\n      if (evt.defaultPrevented && jQueryEvent) {\n        jQueryEvent.preventDefault();\n      }\n      return evt;\n    }\n  };\n  function hydrateObj(obj, meta = {}) {\n    for (const [key, value] of Object.entries(meta)) {\n      try {\n        obj[key] = value;\n      } catch (_unused) {\n        Object.defineProperty(obj, key, {\n          configurable: true,\n          get() {\n            return value;\n          }\n        });\n      }\n    }\n    return obj;\n  }\n\n  return EventHandler;\n\n}));\n//# sourceMappingURL=event-handler.js.map\n", "/*!\n  * Bootstrap manipulator.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Manipulator = factory());\n})(this, (function () { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap dom/manipulator.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  function normalizeData(value) {\n    if (value === 'true') {\n      return true;\n    }\n    if (value === 'false') {\n      return false;\n    }\n    if (value === Number(value).toString()) {\n      return Number(value);\n    }\n    if (value === '' || value === 'null') {\n      return null;\n    }\n    if (typeof value !== 'string') {\n      return value;\n    }\n    try {\n      return JSON.parse(decodeURIComponent(value));\n    } catch (_unused) {\n      return value;\n    }\n  }\n  function normalizeDataKey(key) {\n    return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n  }\n  const Manipulator = {\n    setDataAttribute(element, key, value) {\n      element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n    },\n    removeDataAttribute(element, key) {\n      element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n    },\n    getDataAttributes(element) {\n      if (!element) {\n        return {};\n      }\n      const attributes = {};\n      const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n      for (const key of bsKeys) {\n        let pureKey = key.replace(/^bs/, '');\n        pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n        attributes[pureKey] = normalizeData(element.dataset[key]);\n      }\n      return attributes;\n    },\n    getDataAttribute(element, key) {\n      return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n    }\n  };\n\n  return Manipulator;\n\n}));\n//# sourceMappingURL=manipulator.js.map\n", "/*!\n  * Bootstrap selector-engine.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['../util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SelectorEngine = factory(global.Index));\n})(this, (function (index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap dom/selector-engine.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  const getSelector = element => {\n    let selector = element.getAttribute('data-bs-target');\n    if (!selector || selector === '#') {\n      let hrefAttribute = element.getAttribute('href');\n\n      // The only valid content that could double as a selector are IDs or classes,\n      // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n      // `document.querySelector` will rightfully complain it is invalid.\n      // See https://github.com/twbs/bootstrap/issues/32273\n      if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n        return null;\n      }\n\n      // Just in case some CMS puts out a full URL with the anchor appended\n      if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n        hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n      }\n      selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n    }\n    return selector ? selector.split(',').map(sel => index_js.parseSelector(sel)).join(',') : null;\n  };\n  const SelectorEngine = {\n    find(selector, element = document.documentElement) {\n      return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n    },\n    findOne(selector, element = document.documentElement) {\n      return Element.prototype.querySelector.call(element, selector);\n    },\n    children(element, selector) {\n      return [].concat(...element.children).filter(child => child.matches(selector));\n    },\n    parents(element, selector) {\n      const parents = [];\n      let ancestor = element.parentNode.closest(selector);\n      while (ancestor) {\n        parents.push(ancestor);\n        ancestor = ancestor.parentNode.closest(selector);\n      }\n      return parents;\n    },\n    prev(element, selector) {\n      let previous = element.previousElementSibling;\n      while (previous) {\n        if (previous.matches(selector)) {\n          return [previous];\n        }\n        previous = previous.previousElementSibling;\n      }\n      return [];\n    },\n    // TODO: this is now unused; remove later along with prev()\n    next(element, selector) {\n      let next = element.nextElementSibling;\n      while (next) {\n        if (next.matches(selector)) {\n          return [next];\n        }\n        next = next.nextElementSibling;\n      }\n      return [];\n    },\n    focusableChildren(element) {\n      const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n      return this.find(focusables, element).filter(el => !index_js.isDisabled(el) && index_js.isVisible(el));\n    },\n    getSelectorFromElement(element) {\n      const selector = getSelector(element);\n      if (selector) {\n        return SelectorEngine.findOne(selector) ? selector : null;\n      }\n      return null;\n    },\n    getElementFromSelector(element) {\n      const selector = getSelector(element);\n      return selector ? SelectorEngine.findOne(selector) : null;\n    },\n    getMultipleElementsFromSelector(element) {\n      const selector = getSelector(element);\n      return selector ? SelectorEngine.find(selector) : [];\n    }\n  };\n\n  return SelectorEngine;\n\n}));\n//# sourceMappingURL=selector-engine.js.map\n", "/*!\n  * Bootstrap config.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/manipulator.js'), require('./index.js')) :\n  typeof define === 'function' && define.amd ? define(['../dom/manipulator', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Config = factory(global.Manipulator, global.Index));\n})(this, (function (Manipulator, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/config.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Class definition\n   */\n\n  class Config {\n    // Getters\n    static get Default() {\n      return {};\n    }\n    static get DefaultType() {\n      return {};\n    }\n    static get NAME() {\n      throw new Error('You have to implement the static method \"NAME\", for each component!');\n    }\n    _getConfig(config) {\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n      this._typeCheckConfig(config);\n      return config;\n    }\n    _configAfterMerge(config) {\n      return config;\n    }\n    _mergeConfigObj(config, element) {\n      const jsonConfig = index_js.isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n      return {\n        ...this.constructor.Default,\n        ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n        ...(index_js.isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n        ...(typeof config === 'object' ? config : {})\n      };\n    }\n    _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n      for (const [property, expectedTypes] of Object.entries(configTypes)) {\n        const value = config[property];\n        const valueType = index_js.isElement(value) ? 'element' : index_js.toType(value);\n        if (!new RegExp(expectedTypes).test(valueType)) {\n          throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n        }\n      }\n    }\n  }\n\n  return Config;\n\n}));\n//# sourceMappingURL=config.js.map\n", "/*!\n  * Bootstrap component-functions.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('../dom/event-handler.js'), require('../dom/selector-engine.js'), require('./index.js')) :\n  typeof define === 'function' && define.amd ? define(['exports', '../dom/event-handler', '../dom/selector-engine', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ComponentFunctions = {}, global.EventHandler, global.SelectorEngine, global.Index));\n})(this, (function (exports, EventHandler, SelectorEngine, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/component-functions.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  const enableDismissTrigger = (component, method = 'hide') => {\n    const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n    const name = component.NAME;\n    EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n      if (['A', 'AREA'].includes(this.tagName)) {\n        event.preventDefault();\n      }\n      if (index_js.isDisabled(this)) {\n        return;\n      }\n      const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);\n      const instance = component.getOrCreateInstance(target);\n\n      // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n      instance[method]();\n    });\n  };\n\n  exports.enableDismissTrigger = enableDismissTrigger;\n\n  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\n}));\n//# sourceMappingURL=component-functions.js.map\n", "/*!\n  * Bootstrap backdrop.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('./config.js'), require('./index.js')) :\n  typeof define === 'function' && define.amd ? define(['../dom/event-handler', './config', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Backdrop = factory(global.EventHandler, global.Config, global.Index));\n})(this, (function (EventHandler, Config, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/backdrop.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'backdrop';\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`;\n  const Default = {\n    className: 'modal-backdrop',\n    clickCallback: null,\n    isAnimated: false,\n    isVisible: true,\n    // if false, we use the backdrop helper without adding any element to the dom\n    rootElement: 'body' // give the choice to place backdrop under different elements\n  };\n  const DefaultType = {\n    className: 'string',\n    clickCallback: '(function|null)',\n    isAnimated: 'boolean',\n    isVisible: 'boolean',\n    rootElement: '(element|string)'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Backdrop extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isAppended = false;\n      this._element = null;\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    show(callback) {\n      if (!this._config.isVisible) {\n        index_js.execute(callback);\n        return;\n      }\n      this._append();\n      const element = this._getElement();\n      if (this._config.isAnimated) {\n        index_js.reflow(element);\n      }\n      element.classList.add(CLASS_NAME_SHOW);\n      this._emulateAnimation(() => {\n        index_js.execute(callback);\n      });\n    }\n    hide(callback) {\n      if (!this._config.isVisible) {\n        index_js.execute(callback);\n        return;\n      }\n      this._getElement().classList.remove(CLASS_NAME_SHOW);\n      this._emulateAnimation(() => {\n        this.dispose();\n        index_js.execute(callback);\n      });\n    }\n    dispose() {\n      if (!this._isAppended) {\n        return;\n      }\n      EventHandler.off(this._element, EVENT_MOUSEDOWN);\n      this._element.remove();\n      this._isAppended = false;\n    }\n\n    // Private\n    _getElement() {\n      if (!this._element) {\n        const backdrop = document.createElement('div');\n        backdrop.className = this._config.className;\n        if (this._config.isAnimated) {\n          backdrop.classList.add(CLASS_NAME_FADE);\n        }\n        this._element = backdrop;\n      }\n      return this._element;\n    }\n    _configAfterMerge(config) {\n      // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n      config.rootElement = index_js.getElement(config.rootElement);\n      return config;\n    }\n    _append() {\n      if (this._isAppended) {\n        return;\n      }\n      const element = this._getElement();\n      this._config.rootElement.append(element);\n      EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n        index_js.execute(this._config.clickCallback);\n      });\n      this._isAppended = true;\n    }\n    _emulateAnimation(callback) {\n      index_js.executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n    }\n  }\n\n  return Backdrop;\n\n}));\n//# sourceMappingURL=backdrop.js.map\n", "/*!\n  * Bootstrap focustrap.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('../dom/selector-engine.js'), require('./config.js')) :\n  typeof define === 'function' && define.amd ? define(['../dom/event-handler', '../dom/selector-engine', './config'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Focustrap = factory(global.EventHandler, global.SelectorEngine, global.Config));\n})(this, (function (EventHandler, SelectorEngine, Config) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/focustrap.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'focustrap';\n  const DATA_KEY = 'bs.focustrap';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\n  const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`;\n  const TAB_KEY = 'Tab';\n  const TAB_NAV_FORWARD = 'forward';\n  const TAB_NAV_BACKWARD = 'backward';\n  const Default = {\n    autofocus: true,\n    trapElement: null // The element to trap focus inside of\n  };\n  const DefaultType = {\n    autofocus: 'boolean',\n    trapElement: 'element'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class FocusTrap extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isActive = false;\n      this._lastTabNavDirection = null;\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    activate() {\n      if (this._isActive) {\n        return;\n      }\n      if (this._config.autofocus) {\n        this._config.trapElement.focus();\n      }\n      EventHandler.off(document, EVENT_KEY); // guard against infinite focus loop\n      EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event));\n      EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n      this._isActive = true;\n    }\n    deactivate() {\n      if (!this._isActive) {\n        return;\n      }\n      this._isActive = false;\n      EventHandler.off(document, EVENT_KEY);\n    }\n\n    // Private\n    _handleFocusin(event) {\n      const {\n        trapElement\n      } = this._config;\n      if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n        return;\n      }\n      const elements = SelectorEngine.focusableChildren(trapElement);\n      if (elements.length === 0) {\n        trapElement.focus();\n      } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n        elements[elements.length - 1].focus();\n      } else {\n        elements[0].focus();\n      }\n    }\n    _handleKeydown(event) {\n      if (event.key !== TAB_KEY) {\n        return;\n      }\n      this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n    }\n  }\n\n  return FocusTrap;\n\n}));\n//# sourceMappingURL=focustrap.js.map\n", "/*!\n  * Bootstrap sanitizer.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Sanitizer = {}));\n})(this, (function (exports) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/sanitizer.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  // js-docs-start allow-list\n  const ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\n  const DefaultAllowlist = {\n    // Global attributes allowed on any supplied element below.\n    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n    a: ['target', 'href', 'title', 'rel'],\n    area: [],\n    b: [],\n    br: [],\n    col: [],\n    code: [],\n    dd: [],\n    div: [],\n    dl: [],\n    dt: [],\n    em: [],\n    hr: [],\n    h1: [],\n    h2: [],\n    h3: [],\n    h4: [],\n    h5: [],\n    h6: [],\n    i: [],\n    img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n    li: [],\n    ol: [],\n    p: [],\n    pre: [],\n    s: [],\n    small: [],\n    span: [],\n    sub: [],\n    sup: [],\n    strong: [],\n    u: [],\n    ul: []\n  };\n  // js-docs-end allow-list\n\n  const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n\n  /**\n   * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n   * contexts.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n   */\n  // eslint-disable-next-line unicorn/better-regex\n  const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;\n  const allowedAttribute = (attribute, allowedAttributeList) => {\n    const attributeName = attribute.nodeName.toLowerCase();\n    if (allowedAttributeList.includes(attributeName)) {\n      if (uriAttributes.has(attributeName)) {\n        return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));\n      }\n      return true;\n    }\n\n    // Check if a regular expression validates the attribute.\n    return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n  };\n  function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n    if (!unsafeHtml.length) {\n      return unsafeHtml;\n    }\n    if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n      return sanitizeFunction(unsafeHtml);\n    }\n    const domParser = new window.DOMParser();\n    const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n    const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n    for (const element of elements) {\n      const elementName = element.nodeName.toLowerCase();\n      if (!Object.keys(allowList).includes(elementName)) {\n        element.remove();\n        continue;\n      }\n      const attributeList = [].concat(...element.attributes);\n      const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n      for (const attribute of attributeList) {\n        if (!allowedAttribute(attribute, allowedAttributes)) {\n          element.removeAttribute(attribute.nodeName);\n        }\n      }\n    }\n    return createdDocument.body.innerHTML;\n  }\n\n  exports.DefaultAllowlist = DefaultAllowlist;\n  exports.sanitizeHtml = sanitizeHtml;\n\n  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\n}));\n//# sourceMappingURL=sanitizer.js.map\n", "/*!\n  * Bootstrap scrollbar.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/manipulator.js'), require('../dom/selector-engine.js'), require('./index.js')) :\n  typeof define === 'function' && define.amd ? define(['../dom/manipulator', '../dom/selector-engine', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollbar = factory(global.Manipulator, global.SelectorEngine, global.Index));\n})(this, (function (Manipulator, SelectorEngine, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/scrollBar.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\n  const SELECTOR_STICKY_CONTENT = '.sticky-top';\n  const PROPERTY_PADDING = 'padding-right';\n  const PROPERTY_MARGIN = 'margin-right';\n\n  /**\n   * Class definition\n   */\n\n  class ScrollBarHelper {\n    constructor() {\n      this._element = document.body;\n    }\n\n    // Public\n    getWidth() {\n      // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n      const documentWidth = document.documentElement.clientWidth;\n      return Math.abs(window.innerWidth - documentWidth);\n    }\n    hide() {\n      const width = this.getWidth();\n      this._disableOverFlow();\n      // give padding to element to balance the hidden scrollbar width\n      this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n      // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n      this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n      this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n    }\n    reset() {\n      this._resetElementAttributes(this._element, 'overflow');\n      this._resetElementAttributes(this._element, PROPERTY_PADDING);\n      this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n      this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n    }\n    isOverflowing() {\n      return this.getWidth() > 0;\n    }\n\n    // Private\n    _disableOverFlow() {\n      this._saveInitialAttribute(this._element, 'overflow');\n      this._element.style.overflow = 'hidden';\n    }\n    _setElementAttributes(selector, styleProperty, callback) {\n      const scrollbarWidth = this.getWidth();\n      const manipulationCallBack = element => {\n        if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n          return;\n        }\n        this._saveInitialAttribute(element, styleProperty);\n        const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n        element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n      };\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n    _saveInitialAttribute(element, styleProperty) {\n      const actualValue = element.style.getPropertyValue(styleProperty);\n      if (actualValue) {\n        Manipulator.setDataAttribute(element, styleProperty, actualValue);\n      }\n    }\n    _resetElementAttributes(selector, styleProperty) {\n      const manipulationCallBack = element => {\n        const value = Manipulator.getDataAttribute(element, styleProperty);\n        // We only want to remove the property if the value is `null`; the value can also be zero\n        if (value === null) {\n          element.style.removeProperty(styleProperty);\n          return;\n        }\n        Manipulator.removeDataAttribute(element, styleProperty);\n        element.style.setProperty(styleProperty, value);\n      };\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n    _applyManipulationCallback(selector, callBack) {\n      if (index_js.isElement(selector)) {\n        callBack(selector);\n        return;\n      }\n      for (const sel of SelectorEngine.find(selector, this._element)) {\n        callBack(sel);\n      }\n    }\n  }\n\n  return ScrollBarHelper;\n\n}));\n//# sourceMappingURL=scrollbar.js.map\n", "/*!\n  * Bootstrap swipe.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('./config.js'), require('./index.js')) :\n  typeof define === 'function' && define.amd ? define(['../dom/event-handler', './config', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Swipe = factory(global.EventHandler, global.Config, global.Index));\n})(this, (function (EventHandler, Config, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/swipe.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'swipe';\n  const EVENT_KEY = '.bs.swipe';\n  const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`;\n  const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`;\n  const EVENT_TOUCHEND = `touchend${EVENT_KEY}`;\n  const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`;\n  const EVENT_POINTERUP = `pointerup${EVENT_KEY}`;\n  const POINTER_TYPE_TOUCH = 'touch';\n  const POINTER_TYPE_PEN = 'pen';\n  const CLASS_NAME_POINTER_EVENT = 'pointer-event';\n  const SWIPE_THRESHOLD = 40;\n  const Default = {\n    endCallback: null,\n    leftCallback: null,\n    rightCallback: null\n  };\n  const DefaultType = {\n    endCallback: '(function|null)',\n    leftCallback: '(function|null)',\n    rightCallback: '(function|null)'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Swipe extends Config {\n    constructor(element, config) {\n      super();\n      this._element = element;\n      if (!element || !Swipe.isSupported()) {\n        return;\n      }\n      this._config = this._getConfig(config);\n      this._deltaX = 0;\n      this._supportPointerEvents = Boolean(window.PointerEvent);\n      this._initEvents();\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    dispose() {\n      EventHandler.off(this._element, EVENT_KEY);\n    }\n\n    // Private\n    _start(event) {\n      if (!this._supportPointerEvents) {\n        this._deltaX = event.touches[0].clientX;\n        return;\n      }\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX;\n      }\n    }\n    _end(event) {\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX - this._deltaX;\n      }\n      this._handleSwipe();\n      index_js.execute(this._config.endCallback);\n    }\n    _move(event) {\n      this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n    }\n    _handleSwipe() {\n      const absDeltaX = Math.abs(this._deltaX);\n      if (absDeltaX <= SWIPE_THRESHOLD) {\n        return;\n      }\n      const direction = absDeltaX / this._deltaX;\n      this._deltaX = 0;\n      if (!direction) {\n        return;\n      }\n      index_js.execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n    }\n    _initEvents() {\n      if (this._supportPointerEvents) {\n        EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n        EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n        this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n      } else {\n        EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n        EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n        EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n      }\n    }\n    _eventIsPointerPenTouch(event) {\n      return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n    }\n\n    // Static\n    static isSupported() {\n      return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n    }\n  }\n\n  return Swipe;\n\n}));\n//# sourceMappingURL=swipe.js.map\n", "/*!\n  * Bootstrap template-factory.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/selector-engine.js'), require('./config.js'), require('./sanitizer.js'), require('./index.js')) :\n  typeof define === 'function' && define.amd ? define(['../dom/selector-engine', './config', './sanitizer', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TemplateFactory = factory(global.SelectorEngine, global.Config, global.Sanitizer, global.Index));\n})(this, (function (SelectorEngine, Config, sanitizer_js, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap util/template-factory.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'TemplateFactory';\n  const Default = {\n    allowList: sanitizer_js.DefaultAllowlist,\n    content: {},\n    // { selector : text ,  selector2 : text2 , }\n    extraClass: '',\n    html: false,\n    sanitize: true,\n    sanitizeFn: null,\n    template: '<div></div>'\n  };\n  const DefaultType = {\n    allowList: 'object',\n    content: 'object',\n    extraClass: '(string|function)',\n    html: 'boolean',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    template: 'string'\n  };\n  const DefaultContentType = {\n    entry: '(string|element|function|null)',\n    selector: '(string|element)'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class TemplateFactory extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    getContent() {\n      return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n    }\n    hasContent() {\n      return this.getContent().length > 0;\n    }\n    changeContent(content) {\n      this._checkContent(content);\n      this._config.content = {\n        ...this._config.content,\n        ...content\n      };\n      return this;\n    }\n    toHtml() {\n      const templateWrapper = document.createElement('div');\n      templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n      for (const [selector, text] of Object.entries(this._config.content)) {\n        this._setContent(templateWrapper, text, selector);\n      }\n      const template = templateWrapper.children[0];\n      const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n      if (extraClass) {\n        template.classList.add(...extraClass.split(' '));\n      }\n      return template;\n    }\n\n    // Private\n    _typeCheckConfig(config) {\n      super._typeCheckConfig(config);\n      this._checkContent(config.content);\n    }\n    _checkContent(arg) {\n      for (const [selector, content] of Object.entries(arg)) {\n        super._typeCheckConfig({\n          selector,\n          entry: content\n        }, DefaultContentType);\n      }\n    }\n    _setContent(template, content, selector) {\n      const templateElement = SelectorEngine.findOne(selector, template);\n      if (!templateElement) {\n        return;\n      }\n      content = this._resolvePossibleFunction(content);\n      if (!content) {\n        templateElement.remove();\n        return;\n      }\n      if (index_js.isElement(content)) {\n        this._putElementInTemplate(index_js.getElement(content), templateElement);\n        return;\n      }\n      if (this._config.html) {\n        templateElement.innerHTML = this._maybeSanitize(content);\n        return;\n      }\n      templateElement.textContent = content;\n    }\n    _maybeSanitize(arg) {\n      return this._config.sanitize ? sanitizer_js.sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n    }\n    _resolvePossibleFunction(arg) {\n      return index_js.execute(arg, [this]);\n    }\n    _putElementInTemplate(element, templateElement) {\n      if (this._config.html) {\n        templateElement.innerHTML = '';\n        templateElement.append(element);\n        return;\n      }\n      templateElement.textContent = element.textContent;\n    }\n  }\n\n  return TemplateFactory;\n\n}));\n//# sourceMappingURL=template-factory.js.map\n", "/*!\n  * Bootstrap base-component.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./dom/data.js'), require('./dom/event-handler.js'), require('./util/config.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./dom/data', './dom/event-handler', './util/config', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BaseComponent = factory(global.Data, global.EventHandler, global.Config, global.Index));\n})(this, (function (Data, EventHandler, Config, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap base-component.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const VERSION = '5.3.3';\n\n  /**\n   * Class definition\n   */\n\n  class BaseComponent extends Config {\n    constructor(element, config) {\n      super();\n      element = index_js.getElement(element);\n      if (!element) {\n        return;\n      }\n      this._element = element;\n      this._config = this._getConfig(config);\n      Data.set(this._element, this.constructor.DATA_KEY, this);\n    }\n\n    // Public\n    dispose() {\n      Data.remove(this._element, this.constructor.DATA_KEY);\n      EventHandler.off(this._element, this.constructor.EVENT_KEY);\n      for (const propertyName of Object.getOwnPropertyNames(this)) {\n        this[propertyName] = null;\n      }\n    }\n    _queueCallback(callback, element, isAnimated = true) {\n      index_js.executeAfterTransition(callback, element, isAnimated);\n    }\n    _getConfig(config) {\n      config = this._mergeConfigObj(config, this._element);\n      config = this._configAfterMerge(config);\n      this._typeCheckConfig(config);\n      return config;\n    }\n\n    // Static\n    static getInstance(element) {\n      return Data.get(index_js.getElement(element), this.DATA_KEY);\n    }\n    static getOrCreateInstance(element, config = {}) {\n      return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n    }\n    static get VERSION() {\n      return VERSION;\n    }\n    static get DATA_KEY() {\n      return `bs.${this.NAME}`;\n    }\n    static get EVENT_KEY() {\n      return `.${this.DATA_KEY}`;\n    }\n    static eventName(name) {\n      return `${name}${this.EVENT_KEY}`;\n    }\n  }\n\n  return BaseComponent;\n\n}));\n//# sourceMappingURL=base-component.js.map\n", "/*!\n  * Bootstrap alert.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Alert = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions, global.Index));\n})(this, (function (BaseComponent, EventHandler, componentFunctions_js, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap alert.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'alert';\n  const DATA_KEY = 'bs.alert';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_CLOSE = `close${EVENT_KEY}`;\n  const EVENT_CLOSED = `closed${EVENT_KEY}`;\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n\n  /**\n   * Class definition\n   */\n\n  class Alert extends BaseComponent {\n    // Getters\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    close() {\n      const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n      if (closeEvent.defaultPrevented) {\n        return;\n      }\n      this._element.classList.remove(CLASS_NAME_SHOW);\n      const isAnimated = this._element.classList.contains(CLASS_NAME_FADE);\n      this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n    }\n\n    // Private\n    _destroyElement() {\n      this._element.remove();\n      EventHandler.trigger(this._element, EVENT_CLOSED);\n      this.dispose();\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Alert.getOrCreateInstance(this);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config](this);\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  componentFunctions_js.enableDismissTrigger(Alert, 'close');\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Alert);\n\n  return Alert;\n\n}));\n//# sourceMappingURL=alert.js.map\n", "/*!\n  * Bootstrap button.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Button = factory(global.BaseComponent, global.EventHandler, global.Index));\n})(this, (function (BaseComponent, EventHandler, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap button.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'button';\n  const DATA_KEY = 'bs.button';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const CLASS_NAME_ACTIVE = 'active';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]';\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n\n  /**\n   * Class definition\n   */\n\n  class Button extends BaseComponent {\n    // Getters\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    toggle() {\n      // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n      this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE));\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Button.getOrCreateInstance(this);\n        if (config === 'toggle') {\n          data[config]();\n        }\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n    event.preventDefault();\n    const button = event.target.closest(SELECTOR_DATA_TOGGLE);\n    const data = Button.getOrCreateInstance(button);\n    data.toggle();\n  });\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Button);\n\n  return Button;\n\n}));\n//# sourceMappingURL=button.js.map\n", "/*!\n  * Bootstrap carousel.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js'), require('./util/swipe.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index', './util/swipe'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Carousel = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index, global.Swipe));\n})(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js, Swipe) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap carousel.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'carousel';\n  const DATA_KEY = 'bs.carousel';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const ARROW_LEFT_KEY = 'ArrowLeft';\n  const ARROW_RIGHT_KEY = 'ArrowRight';\n  const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\n  const ORDER_NEXT = 'next';\n  const ORDER_PREV = 'prev';\n  const DIRECTION_LEFT = 'left';\n  const DIRECTION_RIGHT = 'right';\n  const EVENT_SLIDE = `slide${EVENT_KEY}`;\n  const EVENT_SLID = `slid${EVENT_KEY}`;\n  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;\n  const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`;\n  const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`;\n  const EVENT_DRAG_START = `dragstart${EVENT_KEY}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_CAROUSEL = 'carousel';\n  const CLASS_NAME_ACTIVE = 'active';\n  const CLASS_NAME_SLIDE = 'slide';\n  const CLASS_NAME_END = 'carousel-item-end';\n  const CLASS_NAME_START = 'carousel-item-start';\n  const CLASS_NAME_NEXT = 'carousel-item-next';\n  const CLASS_NAME_PREV = 'carousel-item-prev';\n  const SELECTOR_ACTIVE = '.active';\n  const SELECTOR_ITEM = '.carousel-item';\n  const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\n  const SELECTOR_ITEM_IMG = '.carousel-item img';\n  const SELECTOR_INDICATORS = '.carousel-indicators';\n  const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\n  const SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\n  const KEY_TO_DIRECTION = {\n    [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n    [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n  };\n  const Default = {\n    interval: 5000,\n    keyboard: true,\n    pause: 'hover',\n    ride: false,\n    touch: true,\n    wrap: true\n  };\n  const DefaultType = {\n    interval: '(number|boolean)',\n    // TODO:v6 remove boolean support\n    keyboard: 'boolean',\n    pause: '(string|boolean)',\n    ride: '(boolean|string)',\n    touch: 'boolean',\n    wrap: 'boolean'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Carousel extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._interval = null;\n      this._activeElement = null;\n      this._isSliding = false;\n      this.touchTimeout = null;\n      this._swipeHelper = null;\n      this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n      this._addEventListeners();\n      if (this._config.ride === CLASS_NAME_CAROUSEL) {\n        this.cycle();\n      }\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    next() {\n      this._slide(ORDER_NEXT);\n    }\n    nextWhenVisible() {\n      // FIXME TODO use `document.visibilityState`\n      // Don't call next when the page isn't visible\n      // or the carousel or its parent isn't visible\n      if (!document.hidden && index_js.isVisible(this._element)) {\n        this.next();\n      }\n    }\n    prev() {\n      this._slide(ORDER_PREV);\n    }\n    pause() {\n      if (this._isSliding) {\n        index_js.triggerTransitionEnd(this._element);\n      }\n      this._clearInterval();\n    }\n    cycle() {\n      this._clearInterval();\n      this._updateInterval();\n      this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n    }\n    _maybeEnableCycle() {\n      if (!this._config.ride) {\n        return;\n      }\n      if (this._isSliding) {\n        EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n        return;\n      }\n      this.cycle();\n    }\n    to(index) {\n      const items = this._getItems();\n      if (index > items.length - 1 || index < 0) {\n        return;\n      }\n      if (this._isSliding) {\n        EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n        return;\n      }\n      const activeIndex = this._getItemIndex(this._getActive());\n      if (activeIndex === index) {\n        return;\n      }\n      const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n      this._slide(order, items[index]);\n    }\n    dispose() {\n      if (this._swipeHelper) {\n        this._swipeHelper.dispose();\n      }\n      super.dispose();\n    }\n\n    // Private\n    _configAfterMerge(config) {\n      config.defaultInterval = config.interval;\n      return config;\n    }\n    _addEventListeners() {\n      if (this._config.keyboard) {\n        EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n      }\n      if (this._config.pause === 'hover') {\n        EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause());\n        EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle());\n      }\n      if (this._config.touch && Swipe.isSupported()) {\n        this._addTouchEventListeners();\n      }\n    }\n    _addTouchEventListeners() {\n      for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n        EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n      }\n      const endCallBack = () => {\n        if (this._config.pause !== 'hover') {\n          return;\n        }\n\n        // If it's a touch-enabled device, mouseenter/leave are fired as\n        // part of the mouse compatibility events on first tap - the carousel\n        // would stop cycling until user tapped out of it;\n        // here, we listen for touchend, explicitly pause the carousel\n        // (as if it's the second time we tap on it, mouseenter compat event\n        // is NOT fired) and after a timeout (to allow for mouse compatibility\n        // events to fire) we explicitly restart cycling\n\n        this.pause();\n        if (this.touchTimeout) {\n          clearTimeout(this.touchTimeout);\n        }\n        this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n      };\n      const swipeConfig = {\n        leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n        rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n        endCallback: endCallBack\n      };\n      this._swipeHelper = new Swipe(this._element, swipeConfig);\n    }\n    _keydown(event) {\n      if (/input|textarea/i.test(event.target.tagName)) {\n        return;\n      }\n      const direction = KEY_TO_DIRECTION[event.key];\n      if (direction) {\n        event.preventDefault();\n        this._slide(this._directionToOrder(direction));\n      }\n    }\n    _getItemIndex(element) {\n      return this._getItems().indexOf(element);\n    }\n    _setActiveIndicatorElement(index) {\n      if (!this._indicatorsElement) {\n        return;\n      }\n      const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n      activeIndicator.classList.remove(CLASS_NAME_ACTIVE);\n      activeIndicator.removeAttribute('aria-current');\n      const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n      if (newActiveIndicator) {\n        newActiveIndicator.classList.add(CLASS_NAME_ACTIVE);\n        newActiveIndicator.setAttribute('aria-current', 'true');\n      }\n    }\n    _updateInterval() {\n      const element = this._activeElement || this._getActive();\n      if (!element) {\n        return;\n      }\n      const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n      this._config.interval = elementInterval || this._config.defaultInterval;\n    }\n    _slide(order, element = null) {\n      if (this._isSliding) {\n        return;\n      }\n      const activeElement = this._getActive();\n      const isNext = order === ORDER_NEXT;\n      const nextElement = element || index_js.getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n      if (nextElement === activeElement) {\n        return;\n      }\n      const nextElementIndex = this._getItemIndex(nextElement);\n      const triggerEvent = eventName => {\n        return EventHandler.trigger(this._element, eventName, {\n          relatedTarget: nextElement,\n          direction: this._orderToDirection(order),\n          from: this._getItemIndex(activeElement),\n          to: nextElementIndex\n        });\n      };\n      const slideEvent = triggerEvent(EVENT_SLIDE);\n      if (slideEvent.defaultPrevented) {\n        return;\n      }\n      if (!activeElement || !nextElement) {\n        // Some weirdness is happening, so we bail\n        // TODO: change tests that use empty divs to avoid this check\n        return;\n      }\n      const isCycling = Boolean(this._interval);\n      this.pause();\n      this._isSliding = true;\n      this._setActiveIndicatorElement(nextElementIndex);\n      this._activeElement = nextElement;\n      const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n      const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n      nextElement.classList.add(orderClassName);\n      index_js.reflow(nextElement);\n      activeElement.classList.add(directionalClassName);\n      nextElement.classList.add(directionalClassName);\n      const completeCallBack = () => {\n        nextElement.classList.remove(directionalClassName, orderClassName);\n        nextElement.classList.add(CLASS_NAME_ACTIVE);\n        activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName);\n        this._isSliding = false;\n        triggerEvent(EVENT_SLID);\n      };\n      this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n      if (isCycling) {\n        this.cycle();\n      }\n    }\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_SLIDE);\n    }\n    _getActive() {\n      return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n    }\n    _getItems() {\n      return SelectorEngine.find(SELECTOR_ITEM, this._element);\n    }\n    _clearInterval() {\n      if (this._interval) {\n        clearInterval(this._interval);\n        this._interval = null;\n      }\n    }\n    _directionToOrder(direction) {\n      if (index_js.isRTL()) {\n        return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n      }\n      return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n    }\n    _orderToDirection(order) {\n      if (index_js.isRTL()) {\n        return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n      }\n      return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Carousel.getOrCreateInstance(this, config);\n        if (typeof config === 'number') {\n          data.to(config);\n          return;\n        }\n        if (typeof config === 'string') {\n          if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n          data[config]();\n        }\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n    const target = SelectorEngine.getElementFromSelector(this);\n    if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n      return;\n    }\n    event.preventDefault();\n    const carousel = Carousel.getOrCreateInstance(target);\n    const slideIndex = this.getAttribute('data-bs-slide-to');\n    if (slideIndex) {\n      carousel.to(slideIndex);\n      carousel._maybeEnableCycle();\n      return;\n    }\n    if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n      carousel.next();\n      carousel._maybeEnableCycle();\n      return;\n    }\n    carousel.prev();\n    carousel._maybeEnableCycle();\n  });\n  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n    const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n    for (const carousel of carousels) {\n      Carousel.getOrCreateInstance(carousel);\n    }\n  });\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Carousel);\n\n  return Carousel;\n\n}));\n//# sourceMappingURL=carousel.js.map\n", "/*!\n  * Bootstrap collapse.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Collapse = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));\n})(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap collapse.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'collapse';\n  const DATA_KEY = 'bs.collapse';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_COLLAPSE = 'collapse';\n  const CLASS_NAME_COLLAPSING = 'collapsing';\n  const CLASS_NAME_COLLAPSED = 'collapsed';\n  const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\n  const CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\n  const WIDTH = 'width';\n  const HEIGHT = 'height';\n  const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]';\n  const Default = {\n    parent: null,\n    toggle: true\n  };\n  const DefaultType = {\n    parent: '(null|element)',\n    toggle: 'boolean'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Collapse extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._isTransitioning = false;\n      this._triggerArray = [];\n      const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE);\n      for (const elem of toggleList) {\n        const selector = SelectorEngine.getSelectorFromElement(elem);\n        const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n        if (selector !== null && filterElement.length) {\n          this._triggerArray.push(elem);\n        }\n      }\n      this._initializeChildren();\n      if (!this._config.parent) {\n        this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n      }\n      if (this._config.toggle) {\n        this.toggle();\n      }\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    toggle() {\n      if (this._isShown()) {\n        this.hide();\n      } else {\n        this.show();\n      }\n    }\n    show() {\n      if (this._isTransitioning || this._isShown()) {\n        return;\n      }\n      let activeChildren = [];\n\n      // find active children\n      if (this._config.parent) {\n        activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n          toggle: false\n        }));\n      }\n      if (activeChildren.length && activeChildren[0]._isTransitioning) {\n        return;\n      }\n      const startEvent = EventHandler.trigger(this._element, EVENT_SHOW);\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n      for (const activeInstance of activeChildren) {\n        activeInstance.hide();\n      }\n      const dimension = this._getDimension();\n      this._element.classList.remove(CLASS_NAME_COLLAPSE);\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n      this._element.style[dimension] = 0;\n      this._addAriaAndCollapsedClass(this._triggerArray, true);\n      this._isTransitioning = true;\n      const complete = () => {\n        this._isTransitioning = false;\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n        this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW);\n        this._element.style[dimension] = '';\n        EventHandler.trigger(this._element, EVENT_SHOWN);\n      };\n      const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n      const scrollSize = `scroll${capitalizedDimension}`;\n      this._queueCallback(complete, this._element, true);\n      this._element.style[dimension] = `${this._element[scrollSize]}px`;\n    }\n    hide() {\n      if (this._isTransitioning || !this._isShown()) {\n        return;\n      }\n      const startEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n      const dimension = this._getDimension();\n      this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n      index_js.reflow(this._element);\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n      this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW);\n      for (const trigger of this._triggerArray) {\n        const element = SelectorEngine.getElementFromSelector(trigger);\n        if (element && !this._isShown(element)) {\n          this._addAriaAndCollapsedClass([trigger], false);\n        }\n      }\n      this._isTransitioning = true;\n      const complete = () => {\n        this._isTransitioning = false;\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n        this._element.classList.add(CLASS_NAME_COLLAPSE);\n        EventHandler.trigger(this._element, EVENT_HIDDEN);\n      };\n      this._element.style[dimension] = '';\n      this._queueCallback(complete, this._element, true);\n    }\n    _isShown(element = this._element) {\n      return element.classList.contains(CLASS_NAME_SHOW);\n    }\n\n    // Private\n    _configAfterMerge(config) {\n      config.toggle = Boolean(config.toggle); // Coerce string values\n      config.parent = index_js.getElement(config.parent);\n      return config;\n    }\n    _getDimension() {\n      return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n    }\n    _initializeChildren() {\n      if (!this._config.parent) {\n        return;\n      }\n      const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE);\n      for (const element of children) {\n        const selected = SelectorEngine.getElementFromSelector(element);\n        if (selected) {\n          this._addAriaAndCollapsedClass([element], this._isShown(selected));\n        }\n      }\n    }\n    _getFirstLevelChildren(selector) {\n      const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);\n      // remove children if greater depth\n      return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n    }\n    _addAriaAndCollapsedClass(triggerArray, isOpen) {\n      if (!triggerArray.length) {\n        return;\n      }\n      for (const element of triggerArray) {\n        element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n        element.setAttribute('aria-expanded', isOpen);\n      }\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      const _config = {};\n      if (typeof config === 'string' && /show|hide/.test(config)) {\n        _config.toggle = false;\n      }\n      return this.each(function () {\n        const data = Collapse.getOrCreateInstance(this, _config);\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n          data[config]();\n        }\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n    if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n      event.preventDefault();\n    }\n    for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n      Collapse.getOrCreateInstance(element, {\n        toggle: false\n      }).toggle();\n    }\n  });\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Collapse);\n\n  return Collapse;\n\n}));\n//# sourceMappingURL=collapse.js.map\n", "/*!\n  * Bootstrap dropdown.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global.Popper, global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));\n})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';\n\n  function _interopNamespaceDefault(e) {\n    const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });\n    if (e) {\n      for (const k in e) {\n        if (k !== 'default') {\n          const d = Object.getOwnPropertyDescriptor(e, k);\n          Object.defineProperty(n, k, d.get ? d : {\n            enumerable: true,\n            get: () => e[k]\n          });\n        }\n      }\n    }\n    n.default = e;\n    return Object.freeze(n);\n  }\n\n  const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap dropdown.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'dropdown';\n  const DATA_KEY = 'bs.dropdown';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const ESCAPE_KEY = 'Escape';\n  const TAB_KEY = 'Tab';\n  const ARROW_UP_KEY = 'ArrowUp';\n  const ARROW_DOWN_KEY = 'ArrowDown';\n  const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_DROPUP = 'dropup';\n  const CLASS_NAME_DROPEND = 'dropend';\n  const CLASS_NAME_DROPSTART = 'dropstart';\n  const CLASS_NAME_DROPUP_CENTER = 'dropup-center';\n  const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\n  const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;\n  const SELECTOR_MENU = '.dropdown-menu:not(.o-dropdown--menu)'; // Odoo fix task-2764821\n  const SELECTOR_NAVBAR = '.navbar';\n  const SELECTOR_MENU_NOT_SUB = '.dropdown-menu:not(.o-dropdown--menu):not(.o_wysiwyg_submenu)';\n  const SELECTOR_NAVBAR_NAV = '.navbar-nav';\n  const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\n  const PLACEMENT_TOP = index_js.isRTL() ? 'top-end' : 'top-start';\n  const PLACEMENT_TOPEND = index_js.isRTL() ? 'top-start' : 'top-end';\n  const PLACEMENT_BOTTOM = index_js.isRTL() ? 'bottom-end' : 'bottom-start';\n  const PLACEMENT_BOTTOMEND = index_js.isRTL() ? 'bottom-start' : 'bottom-end';\n  const PLACEMENT_RIGHT = index_js.isRTL() ? 'left-start' : 'right-start';\n  const PLACEMENT_LEFT = index_js.isRTL() ? 'right-start' : 'left-start';\n  const PLACEMENT_TOPCENTER = 'top';\n  const PLACEMENT_BOTTOMCENTER = 'bottom';\n  const Default = {\n    autoClose: true,\n    boundary: 'clippingParents',\n    display: 'dynamic',\n    offset: [0, 2],\n    popperConfig: null,\n    reference: 'toggle'\n  };\n  const DefaultType = {\n    autoClose: '(boolean|string)',\n    boundary: '(string|element)',\n    display: 'string',\n    offset: '(array|string|function)',\n    popperConfig: '(null|object|function)',\n    reference: '(string|element|object)'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Dropdown extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._popper = null;\n      this._parent = this._element.parentNode; // dropdown wrapper\n      // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n      this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n      this._inNavbar = this._detectNavbar();\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    toggle() {\n      return this._isShown() ? this.hide() : this.show();\n    }\n    show() {\n      if (index_js.isDisabled(this._element) || this._isShown()) {\n        return;\n      }\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget);\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n      this._createPopper();\n\n      // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n      if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.on(element, 'mouseover', index_js.noop);\n        }\n      }\n      this._element.focus();\n      this._element.setAttribute('aria-expanded', true);\n      this._menu.classList.add(CLASS_NAME_SHOW);\n      this._element.classList.add(CLASS_NAME_SHOW);\n      EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);\n    }\n    hide() {\n      if (index_js.isDisabled(this._element) || !this._isShown()) {\n        return;\n      }\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n      this._completeHide(relatedTarget);\n    }\n    dispose() {\n      if (this._popper) {\n        this._popper.destroy();\n      }\n      super.dispose();\n    }\n    update() {\n      this._inNavbar = this._detectNavbar();\n      if (this._popper) {\n        this._popper.update();\n      }\n    }\n\n    // Private\n    _completeHide(relatedTarget) {\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget);\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.off(element, 'mouseover', index_js.noop);\n        }\n      }\n      if (this._popper) {\n        this._popper.destroy();\n      }\n      this._menu.classList.remove(CLASS_NAME_SHOW);\n      this._element.classList.remove(CLASS_NAME_SHOW);\n      this._element.setAttribute('aria-expanded', 'false');\n      Manipulator.removeDataAttribute(this._menu, 'popper');\n      EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget);\n    }\n    _getConfig(config) {\n      config = super._getConfig(config);\n      if (typeof config.reference === 'object' && !index_js.isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n        // Popper virtual elements require a getBoundingClientRect method\n        throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n      }\n      return config;\n    }\n    _createPopper() {\n      if (typeof Popper__namespace === 'undefined') {\n        throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n      }\n      let referenceElement = this._element;\n      if (this._config.reference === 'parent') {\n        referenceElement = this._parent;\n      } else if (index_js.isElement(this._config.reference)) {\n        referenceElement = index_js.getElement(this._config.reference);\n      } else if (typeof this._config.reference === 'object') {\n        referenceElement = this._config.reference;\n      }\n      const popperConfig = this._getPopperConfig();\n      this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);\n    }\n    _isShown() {\n      return this._menu.classList.contains(CLASS_NAME_SHOW);\n    }\n    _getPlacement() {\n      const parentDropdown = this._parent;\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n        return PLACEMENT_RIGHT;\n      }\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n        return PLACEMENT_LEFT;\n      }\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n        return PLACEMENT_TOPCENTER;\n      }\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n        return PLACEMENT_BOTTOMCENTER;\n      }\n\n      // We need to trim the value because custom properties can also include spaces\n      const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n        return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n      }\n      return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n    }\n    _detectNavbar() {\n      return this._element.closest(SELECTOR_NAVBAR) !== null;\n    }\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n      return offset;\n    }\n    _getPopperConfig() {\n      const defaultBsPopperConfig = {\n        placement: this._getPlacement(),\n        modifiers: [{\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }]\n      };\n\n      // Disable Popper if we have a static display or Dropdown is in Navbar\n      if (this._inNavbar || this._config.display === 'static') {\n        Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove\n        defaultBsPopperConfig.modifiers = [{\n          name: 'applyStyles',\n          enabled: false\n        }];\n      }\n      return {\n        ...defaultBsPopperConfig,\n        ...index_js.execute(this._config.popperConfig, [defaultBsPopperConfig])\n      };\n    }\n    _selectMenuItem({\n      key,\n      target\n    }) {\n      const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));\n      if (!items.length) {\n        return;\n      }\n\n      // if target isn't included in items (e.g. when expanding the dropdown)\n      // allow cycling to get the last item in case key equals ARROW_UP_KEY\n      index_js.getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Dropdown.getOrCreateInstance(this, config);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config]();\n      });\n    }\n    static clearMenus(event) {\n      if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY) {\n        return;\n      }\n      const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n      for (const toggle of openToggles) {\n        const context = Dropdown.getInstance(toggle);\n        if (!context || context._config.autoClose === false) {\n          continue;\n        }\n        const composedPath = event.composedPath();\n        const isMenuTarget = composedPath.includes(context._menu);\n        if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n          continue;\n        }\n\n        // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n        if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n          continue;\n        }\n        const relatedTarget = {\n          relatedTarget: context._element\n        };\n        if (event.type === 'click') {\n          relatedTarget.clickEvent = event;\n        }\n        context._completeHide(relatedTarget);\n      }\n    }\n    static dataApiKeydownHandler(event) {\n      // If not an UP | DOWN | ESCAPE key => not a dropdown command\n      // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n      const isInput = /input|textarea/i.test(event.target.tagName);\n      const isEscapeEvent = event.key === ESCAPE_KEY;\n      const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);\n      if (!isUpOrDownEvent && !isEscapeEvent) {\n        return;\n      }\n      if (isInput && !isEscapeEvent) {\n        return;\n      }\n      event.preventDefault();\n\n      // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n      const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);\n      const instance = Dropdown.getOrCreateInstance(getToggleButton);\n      if (isUpOrDownEvent) {\n        event.stopPropagation();\n        instance.show();\n        instance._selectMenuItem(event);\n        return;\n      }\n      if (instance._isShown()) {\n        // else is escape and we check if it is shown\n        event.stopPropagation();\n        instance.hide();\n        getToggleButton.focus();\n      }\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler);\n  EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU_NOT_SUB, Dropdown.dataApiKeydownHandler);\n  EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus);\n  EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    event.preventDefault();\n    Dropdown.getOrCreateInstance(this).toggle();\n  });\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Dropdown);\n\n  return Dropdown;\n\n}));\n//# sourceMappingURL=dropdown.js.map\n", "/*!\n  * Bootstrap modal.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Modal = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));\n})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap modal.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'modal';\n  const DATA_KEY = 'bs.modal';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const ESCAPE_KEY = 'Escape';\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_RESIZE = `resize${EVENT_KEY}`;\n  const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;\n  const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`;\n  const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_OPEN = 'modal-open';\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_STATIC = 'modal-static';\n  const OPEN_SELECTOR = '.modal.show';\n  const SELECTOR_DIALOG = '.modal-dialog';\n  const SELECTOR_MODAL_BODY = '.modal-body';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]';\n  const Default = {\n    backdrop: true,\n    focus: true,\n    keyboard: true\n  };\n  const DefaultType = {\n    backdrop: '(boolean|string)',\n    focus: 'boolean',\n    keyboard: 'boolean'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Modal extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n      this._isShown = false;\n      this._isTransitioning = false;\n      this._scrollBar = new ScrollBarHelper();\n      this._addEventListeners();\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n    show(relatedTarget) {\n      if (this._isShown || this._isTransitioning) {\n        return;\n      }\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n        relatedTarget\n      });\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n      this._isShown = true;\n      this._isTransitioning = true;\n      this._scrollBar.hide();\n      document.body.classList.add(CLASS_NAME_OPEN);\n      this._adjustDialog();\n      this._backdrop.show(() => this._showElement(relatedTarget));\n    }\n    hide() {\n      if (!this._isShown || this._isTransitioning) {\n        return;\n      }\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n      this._isShown = false;\n      this._isTransitioning = true;\n      this._focustrap.deactivate();\n      this._element.classList.remove(CLASS_NAME_SHOW);\n      this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n    }\n    dispose() {\n      EventHandler.off(window, EVENT_KEY);\n      EventHandler.off(this._dialog, EVENT_KEY);\n      this._backdrop.dispose();\n      this._focustrap.deactivate();\n      super.dispose();\n    }\n    handleUpdate() {\n      this._adjustDialog();\n    }\n\n    // Private\n    _initializeBackDrop() {\n      return new Backdrop({\n        isVisible: Boolean(this._config.backdrop),\n        // 'static' option will be translated to true, and booleans will keep their value,\n        isAnimated: this._isAnimated()\n      });\n    }\n    _initializeFocusTrap() {\n      return new FocusTrap({\n        trapElement: this._element\n      });\n    }\n    _showElement(relatedTarget) {\n      // try to append dynamic modal\n      if (!document.body.contains(this._element)) {\n        document.body.append(this._element);\n      }\n      this._element.style.display = 'block';\n      this._element.removeAttribute('aria-hidden');\n      this._element.setAttribute('aria-modal', true);\n      this._element.setAttribute('role', 'dialog');\n      this._element.scrollTop = 0;\n      const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n      if (modalBody) {\n        modalBody.scrollTop = 0;\n      }\n      index_js.reflow(this._element);\n      this._element.classList.add(CLASS_NAME_SHOW);\n      const transitionComplete = () => {\n        if (this._config?.focus) {\n          this._focustrap.activate();\n        }\n        this._isTransitioning = false;\n        EventHandler.trigger(this._element, EVENT_SHOWN, {\n          relatedTarget\n        });\n      };\n      this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n    }\n    _addEventListeners() {\n      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n        if (event.key !== ESCAPE_KEY) {\n          return;\n        }\n        if (this._config.keyboard) {\n          this.hide();\n          return;\n        }\n        this._triggerBackdropTransition();\n      });\n      EventHandler.on(window, EVENT_RESIZE, () => {\n        if (this._isShown && !this._isTransitioning) {\n          this._adjustDialog();\n        }\n      });\n      EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n        // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n        EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n          if (this._element !== event.target || this._element !== event2.target) {\n            return;\n          }\n          if (this._config.backdrop === 'static') {\n            this._triggerBackdropTransition();\n            return;\n          }\n          if (this._config.backdrop) {\n            this.hide();\n          }\n        });\n      });\n    }\n    _hideModal() {\n      this._element.style.display = 'none';\n      this._element.setAttribute('aria-hidden', true);\n      this._element.removeAttribute('aria-modal');\n      this._element.removeAttribute('role');\n      this._isTransitioning = false;\n      this._backdrop.hide(() => {\n        document.body.classList.remove(CLASS_NAME_OPEN);\n        this._resetAdjustments();\n        this._scrollBar.reset();\n        EventHandler.trigger(this._element, EVENT_HIDDEN);\n      });\n    }\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_FADE);\n    }\n    _triggerBackdropTransition() {\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n      const initialOverflowY = this._element.style.overflowY;\n      // return if the following background transition hasn't yet completed\n      if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n        return;\n      }\n      if (!isModalOverflowing) {\n        this._element.style.overflowY = 'hidden';\n      }\n      this._element.classList.add(CLASS_NAME_STATIC);\n      this._queueCallback(() => {\n        this._element.classList.remove(CLASS_NAME_STATIC);\n        this._queueCallback(() => {\n          this._element.style.overflowY = initialOverflowY;\n        }, this._dialog);\n      }, this._dialog);\n      this._element.focus();\n    }\n\n    /**\n     * The following methods are used to handle overflowing modals\n     */\n\n    _adjustDialog() {\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n      const scrollbarWidth = this._scrollBar.getWidth();\n      const isBodyOverflowing = scrollbarWidth > 0;\n      if (isBodyOverflowing && !isModalOverflowing) {\n        const property = index_js.isRTL() ? 'paddingLeft' : 'paddingRight';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n      if (!isBodyOverflowing && isModalOverflowing) {\n        const property = index_js.isRTL() ? 'paddingRight' : 'paddingLeft';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n    }\n    _resetAdjustments() {\n      this._element.style.paddingLeft = '';\n      this._element.style.paddingRight = '';\n    }\n\n    // Static\n    static jQueryInterface(config, relatedTarget) {\n      return this.each(function () {\n        const data = Modal.getOrCreateInstance(this, config);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config](relatedTarget);\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    const target = SelectorEngine.getElementFromSelector(this);\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n    EventHandler.one(target, EVENT_SHOW, showEvent => {\n      if (showEvent.defaultPrevented) {\n        // only register focus restorer if modal will actually get shown\n        return;\n      }\n      EventHandler.one(target, EVENT_HIDDEN, () => {\n        if (index_js.isVisible(this)) {\n          this.focus();\n        }\n      });\n    });\n\n    // avoid conflict when clicking modal toggler while another one is open\n    const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n    if (alreadyOpen) {\n      Modal.getInstance(alreadyOpen).hide();\n    }\n    const data = Modal.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  componentFunctions_js.enableDismissTrigger(Modal);\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Modal);\n\n  return Modal;\n\n}));\n//# sourceMappingURL=modal.js.map\n", "/*!\n  * Bootstrap offcanvas.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Offcanvas = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));\n})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap offcanvas.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'offcanvas';\n  const DATA_KEY = 'bs.offcanvas';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;\n  const ESCAPE_KEY = 'Escape';\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_SHOWING = 'showing';\n  const CLASS_NAME_HIDING = 'hiding';\n  const CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\n  const OPEN_SELECTOR = '.offcanvas.show';\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_RESIZE = `resize${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]';\n  const Default = {\n    backdrop: true,\n    keyboard: true,\n    scroll: false\n  };\n  const DefaultType = {\n    backdrop: '(boolean|string)',\n    keyboard: 'boolean',\n    scroll: 'boolean'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Offcanvas extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._isShown = false;\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n      this._addEventListeners();\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n    show(relatedTarget) {\n      if (this._isShown) {\n        return;\n      }\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n        relatedTarget\n      });\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n      this._isShown = true;\n      this._backdrop.show();\n      if (!this._config.scroll) {\n        new ScrollBarHelper().hide();\n      }\n      this._element.setAttribute('aria-modal', true);\n      this._element.setAttribute('role', 'dialog');\n      this._element.classList.add(CLASS_NAME_SHOWING);\n      const completeCallBack = () => {\n        if (!this._config.scroll || this._config.backdrop) {\n          this._focustrap.activate();\n        }\n        this._element.classList.add(CLASS_NAME_SHOW);\n        this._element.classList.remove(CLASS_NAME_SHOWING);\n        EventHandler.trigger(this._element, EVENT_SHOWN, {\n          relatedTarget\n        });\n      };\n      this._queueCallback(completeCallBack, this._element, true);\n    }\n    hide() {\n      if (!this._isShown) {\n        return;\n      }\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n      this._focustrap.deactivate();\n      this._element.blur();\n      this._isShown = false;\n      this._element.classList.add(CLASS_NAME_HIDING);\n      this._backdrop.hide();\n      const completeCallback = () => {\n        this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING);\n        this._element.removeAttribute('aria-modal');\n        this._element.removeAttribute('role');\n        if (!this._config.scroll) {\n          new ScrollBarHelper().reset();\n        }\n        EventHandler.trigger(this._element, EVENT_HIDDEN);\n      };\n      this._queueCallback(completeCallback, this._element, true);\n    }\n    dispose() {\n      this._backdrop.dispose();\n      this._focustrap.deactivate();\n      super.dispose();\n    }\n\n    // Private\n    _initializeBackDrop() {\n      const clickCallback = () => {\n        if (this._config.backdrop === 'static') {\n          EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n        this.hide();\n      };\n\n      // 'static' option will be translated to true, and booleans will keep their value\n      const isVisible = Boolean(this._config.backdrop);\n      return new Backdrop({\n        className: CLASS_NAME_BACKDROP,\n        isVisible,\n        isAnimated: true,\n        rootElement: this._element.parentNode,\n        clickCallback: isVisible ? clickCallback : null\n      });\n    }\n    _initializeFocusTrap() {\n      return new FocusTrap({\n        trapElement: this._element\n      });\n    }\n    _addEventListeners() {\n      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n        if (event.key !== ESCAPE_KEY) {\n          return;\n        }\n        if (this._config.keyboard) {\n          this.hide();\n          return;\n        }\n        EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n      });\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Offcanvas.getOrCreateInstance(this, config);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config](this);\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    const target = SelectorEngine.getElementFromSelector(this);\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n    if (index_js.isDisabled(this)) {\n      return;\n    }\n    EventHandler.one(target, EVENT_HIDDEN, () => {\n      // focus on trigger when it is closed\n      if (index_js.isVisible(this)) {\n        this.focus();\n      }\n    });\n\n    // avoid conflict when clicking a toggler of an offcanvas, while another is open\n    const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n    if (alreadyOpen && alreadyOpen !== target) {\n      Offcanvas.getInstance(alreadyOpen).hide();\n    }\n    const data = Offcanvas.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n      Offcanvas.getOrCreateInstance(selector).show();\n    }\n  });\n  EventHandler.on(window, EVENT_RESIZE, () => {\n    for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n      if (getComputedStyle(element).position !== 'fixed') {\n        Offcanvas.getOrCreateInstance(element).hide();\n      }\n    }\n  });\n  componentFunctions_js.enableDismissTrigger(Offcanvas);\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Offcanvas);\n\n  return Offcanvas;\n\n}));\n//# sourceMappingURL=offcanvas.js.map\n", "/*!\n  * Bootstrap tooltip.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./util/index.js'), require('./util/sanitizer.js'), require('./util/template-factory.js')) :\n  typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './util/index', './util/sanitizer', './util/template-factory'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global.Popper, global.BaseComponent, global.EventHandler, global.Manipulator, global.Index, global.Sanitizer, global.TemplateFactory));\n})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, index_js, sanitizer_js, TemplateFactory) { 'use strict';\n\n  function _interopNamespaceDefault(e) {\n    const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });\n    if (e) {\n      for (const k in e) {\n        if (k !== 'default') {\n          const d = Object.getOwnPropertyDescriptor(e, k);\n          Object.defineProperty(n, k, d.get ? d : {\n            enumerable: true,\n            get: () => e[k]\n          });\n        }\n      }\n    }\n    n.default = e;\n    return Object.freeze(n);\n  }\n\n  const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap tooltip.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'tooltip';\n  const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_MODAL = 'modal';\n  const CLASS_NAME_SHOW = 'show';\n  const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\n  const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\n  const EVENT_MODAL_HIDE = 'hide.bs.modal';\n  const TRIGGER_HOVER = 'hover';\n  const TRIGGER_FOCUS = 'focus';\n  const TRIGGER_CLICK = 'click';\n  const TRIGGER_MANUAL = 'manual';\n  const EVENT_HIDE = 'hide';\n  const EVENT_HIDDEN = 'hidden';\n  const EVENT_SHOW = 'show';\n  const EVENT_SHOWN = 'shown';\n  const EVENT_INSERTED = 'inserted';\n  const EVENT_CLICK = 'click';\n  const EVENT_FOCUSIN = 'focusin';\n  const EVENT_FOCUSOUT = 'focusout';\n  const EVENT_MOUSEENTER = 'mouseenter';\n  const EVENT_MOUSELEAVE = 'mouseleave';\n  const AttachmentMap = {\n    AUTO: 'auto',\n    TOP: 'top',\n    RIGHT: index_js.isRTL() ? 'left' : 'right',\n    BOTTOM: 'bottom',\n    LEFT: index_js.isRTL() ? 'right' : 'left'\n  };\n  const Default = {\n    allowList: sanitizer_js.DefaultAllowlist,\n    animation: true,\n    boundary: 'clippingParents',\n    container: false,\n    customClass: '',\n    delay: 0,\n    fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n    html: false,\n    offset: [0, 6],\n    placement: 'top',\n    popperConfig: null,\n    sanitize: true,\n    sanitizeFn: null,\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\">' + '<div class=\"tooltip-arrow\"></div>' + '<div class=\"tooltip-inner\"></div>' + '</div>',\n    title: '',\n    trigger: 'hover focus'\n  };\n  const DefaultType = {\n    allowList: 'object',\n    animation: 'boolean',\n    boundary: '(string|element)',\n    container: '(string|element|boolean)',\n    customClass: '(string|function)',\n    delay: '(number|object)',\n    fallbackPlacements: 'array',\n    html: 'boolean',\n    offset: '(array|string|function)',\n    placement: '(string|function)',\n    popperConfig: '(null|object|function)',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    selector: '(string|boolean)',\n    template: 'string',\n    title: '(string|element|function)',\n    trigger: 'string'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Tooltip extends BaseComponent {\n    constructor(element, config) {\n      if (typeof Popper__namespace === 'undefined') {\n        throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n      }\n      super(element, config);\n\n      // Private\n      this._isEnabled = true;\n      this._timeout = 0;\n      this._isHovered = null;\n      this._activeTrigger = {};\n      this._popper = null;\n      this._templateFactory = null;\n      this._newContent = null;\n\n      // Protected\n      this.tip = null;\n      this._setListeners();\n      if (!this._config.selector) {\n        this._fixTitle();\n      }\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    enable() {\n      this._isEnabled = true;\n    }\n    disable() {\n      this._isEnabled = false;\n    }\n    toggleEnabled() {\n      this._isEnabled = !this._isEnabled;\n    }\n    toggle() {\n      if (!this._isEnabled) {\n        return;\n      }\n      this._activeTrigger.click = !this._activeTrigger.click;\n      if (this._isShown()) {\n        this._leave();\n        return;\n      }\n      this._enter();\n    }\n    dispose() {\n      clearTimeout(this._timeout);\n      EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n      if (this._element.getAttribute('data-bs-original-title')) {\n        this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n      }\n      this._disposePopper();\n      super.dispose();\n    }\n    show() {\n      if (this._element.style.display === 'none') {\n        throw new Error('Please use show on visible elements');\n      }\n      if (!(this._isWithContent() && this._isEnabled)) {\n        return;\n      }\n      const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW));\n      const shadowRoot = index_js.findShadowRoot(this._element);\n      const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n      if (showEvent.defaultPrevented || !isInTheDom) {\n        return;\n      }\n\n      // TODO: v6 remove this or make it optional\n      this._disposePopper();\n      const tip = this._getTipElement();\n      this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n      const {\n        container\n      } = this._config;\n      if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n        container.append(tip);\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n      }\n      this._popper = this._createPopper(tip);\n      tip.classList.add(CLASS_NAME_SHOW);\n\n      // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.on(element, 'mouseover', index_js.noop);\n        }\n      }\n      const complete = () => {\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN));\n        if (this._isHovered === false) {\n          this._leave();\n        }\n        this._isHovered = false;\n      };\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n    hide() {\n      if (!this._isShown()) {\n        return;\n      }\n      const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE));\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n      const tip = this._getTipElement();\n      tip.classList.remove(CLASS_NAME_SHOW);\n\n      // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.off(element, 'mouseover', index_js.noop);\n        }\n      }\n      this._activeTrigger[TRIGGER_CLICK] = false;\n      this._activeTrigger[TRIGGER_FOCUS] = false;\n      this._activeTrigger[TRIGGER_HOVER] = false;\n      this._isHovered = null; // it is a trick to support manual triggering\n\n      const complete = () => {\n        if (this._isWithActiveTrigger()) {\n          return;\n        }\n        if (!this._isHovered) {\n          this._disposePopper();\n        }\n        this._element.removeAttribute('aria-describedby');\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN));\n      };\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n    update() {\n      if (this._popper) {\n        this._popper.update();\n      }\n    }\n\n    // Protected\n    _isWithContent() {\n      return Boolean(this._getTitle());\n    }\n    _getTipElement() {\n      if (!this.tip) {\n        this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n      }\n      return this.tip;\n    }\n    _createTipElement(content) {\n      const tip = this._getTemplateFactory(content).toHtml();\n\n      // TODO: remove this check in v6\n      if (!tip) {\n        return null;\n      }\n      tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW);\n      // TODO: v6 the following can be achieved with CSS only\n      tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n      const tipId = index_js.getUID(this.constructor.NAME).toString();\n      tip.setAttribute('id', tipId);\n      if (this._isAnimated()) {\n        tip.classList.add(CLASS_NAME_FADE);\n      }\n      return tip;\n    }\n    setContent(content) {\n      this._newContent = content;\n      if (this._isShown()) {\n        this._disposePopper();\n        this.show();\n      }\n    }\n    _getTemplateFactory(content) {\n      if (this._templateFactory) {\n        this._templateFactory.changeContent(content);\n      } else {\n        this._templateFactory = new TemplateFactory({\n          ...this._config,\n          // the `content` var has to be after `this._config`\n          // to override config.content in case of popover\n          content,\n          extraClass: this._resolvePossibleFunction(this._config.customClass)\n        });\n      }\n      return this._templateFactory;\n    }\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n      };\n    }\n    _getTitle() {\n      return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n    }\n\n    // Private\n    _initializeOnDelegatedTarget(event) {\n      return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n    }\n    _isAnimated() {\n      return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE);\n    }\n    _isShown() {\n      return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW);\n    }\n    _createPopper(tip) {\n      const placement = index_js.execute(this._config.placement, [this, tip, this._element]);\n      const attachment = AttachmentMap[placement.toUpperCase()];\n      return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment));\n    }\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n      return offset;\n    }\n    _resolvePossibleFunction(arg) {\n      return index_js.execute(arg, [this._element]);\n    }\n    _getPopperConfig(attachment) {\n      const defaultBsPopperConfig = {\n        placement: attachment,\n        modifiers: [{\n          name: 'flip',\n          options: {\n            fallbackPlacements: this._config.fallbackPlacements\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }, {\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'arrow',\n          options: {\n            element: `.${this.constructor.NAME}-arrow`\n          }\n        }, {\n          name: 'preSetPlacement',\n          enabled: true,\n          phase: 'beforeMain',\n          fn: data => {\n            // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n            // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n            this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n          }\n        }]\n      };\n      return {\n        ...defaultBsPopperConfig,\n        ...index_js.execute(this._config.popperConfig, [defaultBsPopperConfig])\n      };\n    }\n    _setListeners() {\n      const triggers = this._config.trigger.split(' ');\n      for (const trigger of triggers) {\n        if (trigger === 'click') {\n          EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n            context.toggle();\n          });\n        } else if (trigger !== TRIGGER_MANUAL) {\n          const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN);\n          const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT);\n          EventHandler.on(this._element, eventIn, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n            context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n            context._enter();\n          });\n          EventHandler.on(this._element, eventOut, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n            context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n            context._leave();\n          });\n        }\n      }\n      this._hideModalHandler = () => {\n        if (this._element) {\n          this.hide();\n        }\n      };\n      EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n    }\n    _fixTitle() {\n      const title = this._element.getAttribute('title');\n      if (!title) {\n        return;\n      }\n      if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n        this._element.setAttribute('aria-label', title);\n      }\n      this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n      this._element.removeAttribute('title');\n    }\n    _enter() {\n      if (this._isShown() || this._isHovered) {\n        this._isHovered = true;\n        return;\n      }\n      this._isHovered = true;\n      this._setTimeout(() => {\n        if (this._isHovered) {\n          this.show();\n        }\n      }, this._config.delay.show);\n    }\n    _leave() {\n      if (this._isWithActiveTrigger()) {\n        return;\n      }\n      this._isHovered = false;\n      this._setTimeout(() => {\n        if (!this._isHovered) {\n          this.hide();\n        }\n      }, this._config.delay.hide);\n    }\n    _setTimeout(handler, timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = setTimeout(handler, timeout);\n    }\n    _isWithActiveTrigger() {\n      return Object.values(this._activeTrigger).includes(true);\n    }\n    _getConfig(config) {\n      const dataAttributes = Manipulator.getDataAttributes(this._element);\n      for (const dataAttribute of Object.keys(dataAttributes)) {\n        if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n          delete dataAttributes[dataAttribute];\n        }\n      }\n      config = {\n        ...dataAttributes,\n        ...(typeof config === 'object' && config ? config : {})\n      };\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n      this._typeCheckConfig(config);\n      return config;\n    }\n    _configAfterMerge(config) {\n      config.container = config.container === false ? document.body : index_js.getElement(config.container);\n      if (typeof config.delay === 'number') {\n        config.delay = {\n          show: config.delay,\n          hide: config.delay\n        };\n      }\n      if (typeof config.title === 'number') {\n        config.title = config.title.toString();\n      }\n      if (typeof config.content === 'number') {\n        config.content = config.content.toString();\n      }\n      return config;\n    }\n    _getDelegateConfig() {\n      const config = {};\n      for (const [key, value] of Object.entries(this._config)) {\n        if (this.constructor.Default[key] !== value) {\n          config[key] = value;\n        }\n      }\n      config.selector = false;\n      config.trigger = 'manual';\n\n      // In the future can be replaced with:\n      // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n      // `Object.fromEntries(keysWithDifferentValues)`\n      return config;\n    }\n    _disposePopper() {\n      if (this._popper) {\n        this._popper.destroy();\n        this._popper = null;\n      }\n      if (this.tip) {\n        this.tip.remove();\n        this.tip = null;\n      }\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tooltip.getOrCreateInstance(this, config);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config]();\n      });\n    }\n  }\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Tooltip);\n\n  return Tooltip;\n\n}));\n//# sourceMappingURL=tooltip.js.map\n", "/*!\n  * Bootstrap popover.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./tooltip', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip, global.Index));\n})(this, (function (Tooltip, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap popover.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'popover';\n  const SELECTOR_TITLE = '.popover-header';\n  const SELECTOR_CONTENT = '.popover-body';\n  const Default = {\n    ...Tooltip.Default,\n    content: '',\n    offset: [0, 8],\n    placement: 'right',\n    template: '<div class=\"popover\" role=\"tooltip\">' + '<div class=\"popover-arrow\"></div>' + '<h3 class=\"popover-header\"></h3>' + '<div class=\"popover-body\"></div>' + '</div>',\n    trigger: 'click'\n  };\n  const DefaultType = {\n    ...Tooltip.DefaultType,\n    content: '(null|string|element|function)'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Popover extends Tooltip {\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Overrides\n    _isWithContent() {\n      return this._getTitle() || this._getContent();\n    }\n\n    // Private\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TITLE]: this._getTitle(),\n        [SELECTOR_CONTENT]: this._getContent()\n      };\n    }\n    _getContent() {\n      return this._resolvePossibleFunction(this._config.content);\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Popover.getOrCreateInstance(this, config);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config]();\n      });\n    }\n  }\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Popover);\n\n  return Popover;\n\n}));\n//# sourceMappingURL=popover.js.map\n", "/*!\n  * Bootstrap scrollspy.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ScrollSpy = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));\n})(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap scrollspy.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'scrollspy';\n  const DATA_KEY = 'bs.scrollspy';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_ACTIVATE = `activate${EVENT_KEY}`;\n  const EVENT_CLICK = `click${EVENT_KEY}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\n  const CLASS_NAME_ACTIVE = 'active';\n  const SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\n  const SELECTOR_TARGET_LINKS = '[href]';\n  const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\n  const SELECTOR_NAV_LINKS = '.nav-link';\n  const SELECTOR_NAV_ITEMS = '.nav-item';\n  const SELECTOR_LIST_ITEMS = '.list-group-item';\n  const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\n  const SELECTOR_DROPDOWN = '.dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\n  const Default = {\n    offset: null,\n    // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: '0px 0px -25%',\n    smoothScroll: false,\n    target: null,\n    threshold: [0.1, 0.5, 1]\n  };\n  const DefaultType = {\n    offset: '(number|null)',\n    // TODO v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: 'string',\n    smoothScroll: 'boolean',\n    target: 'element',\n    threshold: 'array'\n  };\n\n  /**\n   * Class definition\n   */\n\n  class ScrollSpy extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n\n      // this._element is the observablesContainer and config.target the menu links wrapper\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n      this._activeTarget = null;\n      this._observer = null;\n      this._previousScrollData = {\n        visibleEntryTop: 0,\n        parentScrollTop: 0\n      };\n      this.refresh(); // initialize\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    refresh() {\n      this._initializeTargetsAndObservables();\n      this._maybeEnableSmoothScroll();\n      if (this._observer) {\n        this._observer.disconnect();\n      } else {\n        this._observer = this._getNewObserver();\n      }\n      for (const section of this._observableSections.values()) {\n        this._observer.observe(section);\n      }\n    }\n    dispose() {\n      this._observer.disconnect();\n      super.dispose();\n    }\n\n    // Private\n    _configAfterMerge(config) {\n      // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n      config.target = index_js.getElement(config.target) || document.body;\n\n      // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n      config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n      if (typeof config.threshold === 'string') {\n        config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n      }\n      return config;\n    }\n    _maybeEnableSmoothScroll() {\n      if (!this._config.smoothScroll) {\n        return;\n      }\n\n      // unregister any previous listeners\n      EventHandler.off(this._config.target, EVENT_CLICK);\n      EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n        const observableSection = this._observableSections.get(event.target.hash);\n        if (observableSection) {\n          event.preventDefault();\n          const root = this._rootElement || window;\n          const height = observableSection.offsetTop - this._element.offsetTop;\n          if (root.scrollTo) {\n            root.scrollTo({\n              top: height,\n              behavior: 'smooth'\n            });\n            return;\n          }\n\n          // Chrome 60 doesn't support `scrollTo`\n          root.scrollTop = height;\n        }\n      });\n    }\n    _getNewObserver() {\n      const options = {\n        root: this._rootElement,\n        threshold: this._config.threshold,\n        rootMargin: this._config.rootMargin\n      };\n      return new IntersectionObserver(entries => this._observerCallback(entries), options);\n    }\n\n    // The logic of selection\n    _observerCallback(entries) {\n      const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n      const activate = entry => {\n        this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n        this._process(targetElement(entry));\n      };\n      const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n      const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n      this._previousScrollData.parentScrollTop = parentScrollTop;\n      for (const entry of entries) {\n        if (!entry.isIntersecting) {\n          this._activeTarget = null;\n          this._clearActiveClass(targetElement(entry));\n          continue;\n        }\n        const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;\n        // if we are scrolling down, pick the bigger offsetTop\n        if (userScrollsDown && entryIsLowerThanPrevious) {\n          activate(entry);\n          // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n          if (!parentScrollTop) {\n            return;\n          }\n          continue;\n        }\n\n        // if we are scrolling up, pick the smallest offsetTop\n        if (!userScrollsDown && !entryIsLowerThanPrevious) {\n          activate(entry);\n        }\n      }\n    }\n    _initializeTargetsAndObservables() {\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n      for (const anchor of targetLinks) {\n        // ensure that the anchor has an id and is not disabled\n        if (!anchor.hash || index_js.isDisabled(anchor)) {\n          continue;\n        }\n        const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);\n\n        // ensure that the observableSection exists & is visible\n        if (index_js.isVisible(observableSection)) {\n          this._targetLinks.set(decodeURI(anchor.hash), anchor);\n          this._observableSections.set(anchor.hash, observableSection);\n        }\n      }\n    }\n    _process(target) {\n      if (this._activeTarget === target) {\n        return;\n      }\n      this._clearActiveClass(this._config.target);\n      this._activeTarget = target;\n      target.classList.add(CLASS_NAME_ACTIVE);\n      this._activateParents(target);\n      EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n        relatedTarget: target\n      });\n    }\n    _activateParents(target) {\n      // Activate dropdown parents\n      if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n        SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE);\n        return;\n      }\n      for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n        // Set triggered links parents as active\n        // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n        for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n          item.classList.add(CLASS_NAME_ACTIVE);\n        }\n      }\n    }\n    _clearActiveClass(parent) {\n      parent.classList.remove(CLASS_NAME_ACTIVE);\n      const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent);\n      for (const node of activeNodes) {\n        node.classList.remove(CLASS_NAME_ACTIVE);\n      }\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = ScrollSpy.getOrCreateInstance(this, config);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config]();\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n      ScrollSpy.getOrCreateInstance(spy);\n    }\n  });\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(ScrollSpy);\n\n  return ScrollSpy;\n\n}));\n//# sourceMappingURL=scrollspy.js.map\n", "/*!\n  * Bootstrap tab.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tab = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));\n})(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap tab.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'tab';\n  const DATA_KEY = 'bs.tab';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}`;\n  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`;\n  const ARROW_LEFT_KEY = 'ArrowLeft';\n  const ARROW_RIGHT_KEY = 'ArrowRight';\n  const ARROW_UP_KEY = 'ArrowUp';\n  const ARROW_DOWN_KEY = 'ArrowDown';\n  const HOME_KEY = 'Home';\n  const END_KEY = 'End';\n  const CLASS_NAME_ACTIVE = 'active';\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_DROPDOWN = 'dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\n  const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';\n  const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`;\n  const SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]';\n  const SELECTOR_OUTER = '.nav-item, .list-group-item';\n  const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]'; // TODO: could only be `tab` in v6\n  const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`;\n\n  /**\n   * Class definition\n   */\n\n  class Tab extends BaseComponent {\n    constructor(element) {\n      super(element);\n      this._parent = this._element.closest(SELECTOR_TAB_PANEL);\n      if (!this._parent) {\n        return;\n        // TODO: should throw exception in v6\n        // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n      }\n\n      // Set up initial aria attributes\n      this._setInitialAttributes(this._parent, this._getChildren());\n      EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n    }\n\n    // Getters\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    show() {\n      // Shows this elem and deactivate the active sibling if exists\n      const innerElem = this._element;\n      if (this._elemIsActive(innerElem)) {\n        return;\n      }\n\n      // Search for active tab on same parent to deactivate it\n      const active = this._getActiveElem();\n      const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE, {\n        relatedTarget: innerElem\n      }) : null;\n      const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, {\n        relatedTarget: active\n      });\n      if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {\n        return;\n      }\n      this._deactivate(active, innerElem);\n      this._activate(innerElem, active);\n    }\n\n    // Private\n    _activate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n      element.classList.add(CLASS_NAME_ACTIVE);\n      this._activate(SelectorEngine.getElementFromSelector(element)); // Search and activate/show the proper section\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.add(CLASS_NAME_SHOW);\n          return;\n        }\n        element.removeAttribute('tabindex');\n        element.setAttribute('aria-selected', true);\n        this._toggleDropDown(element, true);\n        EventHandler.trigger(element, EVENT_SHOWN, {\n          relatedTarget: relatedElem\n        });\n      };\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));\n    }\n    _deactivate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n      element.classList.remove(CLASS_NAME_ACTIVE);\n      element.blur();\n      this._deactivate(SelectorEngine.getElementFromSelector(element)); // Search and deactivate the shown section too\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.remove(CLASS_NAME_SHOW);\n          return;\n        }\n        element.setAttribute('aria-selected', false);\n        element.setAttribute('tabindex', '-1');\n        this._toggleDropDown(element, false);\n        EventHandler.trigger(element, EVENT_HIDDEN, {\n          relatedTarget: relatedElem\n        });\n      };\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));\n    }\n    _keydown(event) {\n      if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key)) {\n        return;\n      }\n      event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n      event.preventDefault();\n      const children = this._getChildren().filter(element => !index_js.isDisabled(element));\n      let nextActiveElement;\n      if ([HOME_KEY, END_KEY].includes(event.key)) {\n        nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1];\n      } else {\n        const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);\n        nextActiveElement = index_js.getNextActiveElement(children, event.target, isNext, true);\n      }\n      if (nextActiveElement) {\n        nextActiveElement.focus({\n          preventScroll: true\n        });\n        Tab.getOrCreateInstance(nextActiveElement).show();\n      }\n    }\n    _getChildren() {\n      // collection of inner elements\n      return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent);\n    }\n    _getActiveElem() {\n      return this._getChildren().find(child => this._elemIsActive(child)) || null;\n    }\n    _setInitialAttributes(parent, children) {\n      this._setAttributeIfNotExists(parent, 'role', 'tablist');\n      for (const child of children) {\n        this._setInitialAttributesOnChild(child);\n      }\n    }\n    _setInitialAttributesOnChild(child) {\n      child = this._getInnerElement(child);\n      const isActive = this._elemIsActive(child);\n      const outerElem = this._getOuterElement(child);\n      child.setAttribute('aria-selected', isActive);\n      if (outerElem !== child) {\n        this._setAttributeIfNotExists(outerElem, 'role', 'presentation');\n      }\n      if (!isActive) {\n        child.setAttribute('tabindex', '-1');\n      }\n      this._setAttributeIfNotExists(child, 'role', 'tab');\n\n      // set attributes to the related panel too\n      this._setInitialAttributesOnTargetPanel(child);\n    }\n    _setInitialAttributesOnTargetPanel(child) {\n      const target = SelectorEngine.getElementFromSelector(child);\n      if (!target) {\n        return;\n      }\n      this._setAttributeIfNotExists(target, 'role', 'tabpanel');\n      if (child.id) {\n        this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`);\n      }\n    }\n    _toggleDropDown(element, open) {\n      const outerElem = this._getOuterElement(element);\n      if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n        return;\n      }\n      const toggle = (selector, className) => {\n        const element = SelectorEngine.findOne(selector, outerElem);\n        if (element) {\n          element.classList.toggle(className, open);\n        }\n      };\n      toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);\n      toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW);\n      outerElem.setAttribute('aria-expanded', open);\n    }\n    _setAttributeIfNotExists(element, attribute, value) {\n      if (!element.hasAttribute(attribute)) {\n        element.setAttribute(attribute, value);\n      }\n    }\n    _elemIsActive(elem) {\n      return elem.classList.contains(CLASS_NAME_ACTIVE);\n    }\n\n    // Try to get the inner element (usually the .nav-link)\n    _getInnerElement(elem) {\n      return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem);\n    }\n\n    // Try to get the outer element (usually the .nav-item)\n    _getOuterElement(elem) {\n      return elem.closest(SELECTOR_OUTER) || elem;\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tab.getOrCreateInstance(this);\n        if (typeof config !== 'string') {\n          return;\n        }\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n        data[config]();\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n    if (index_js.isDisabled(this)) {\n      return;\n    }\n    Tab.getOrCreateInstance(this).show();\n  });\n\n  /**\n   * Initialize on focus\n   */\n  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n      Tab.getOrCreateInstance(element);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Tab);\n\n  return Tab;\n\n}));\n//# sourceMappingURL=tab.js.map\n", "/*!\n  * Bootstrap toast.js v5.3.3 (https://getbootstrap.com/)\n  * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js'), require('./util/index.js')) :\n  typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions', './util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Toast = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions, global.Index));\n})(this, (function (BaseComponent, EventHandler, componentFunctions_js, index_js) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap toast.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n\n  /**\n   * Constants\n   */\n\n  const NAME = 'toast';\n  const DATA_KEY = 'bs.toast';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;\n  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;\n  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\n  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_HIDE = 'hide'; // @deprecated - kept here only for backwards compatibility\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_SHOWING = 'showing';\n  const DefaultType = {\n    animation: 'boolean',\n    autohide: 'boolean',\n    delay: 'number'\n  };\n  const Default = {\n    animation: true,\n    autohide: true,\n    delay: 5000\n  };\n\n  /**\n   * Class definition\n   */\n\n  class Toast extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._timeout = null;\n      this._hasMouseInteraction = false;\n      this._hasKeyboardInteraction = false;\n      this._setListeners();\n    }\n\n    // Getters\n    static get Default() {\n      return Default;\n    }\n    static get DefaultType() {\n      return DefaultType;\n    }\n    static get NAME() {\n      return NAME;\n    }\n\n    // Public\n    show() {\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n      this._clearTimeout();\n      if (this._config.animation) {\n        this._element.classList.add(CLASS_NAME_FADE);\n      }\n      const complete = () => {\n        this._element.classList.remove(CLASS_NAME_SHOWING);\n        EventHandler.trigger(this._element, EVENT_SHOWN);\n        this._maybeScheduleHide();\n      };\n      this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated\n      index_js.reflow(this._element);\n      this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING);\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n    hide() {\n      if (!this.isShown()) {\n        return;\n      }\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n      const complete = () => {\n        this._element.classList.add(CLASS_NAME_HIDE); // @deprecated\n        this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW);\n        EventHandler.trigger(this._element, EVENT_HIDDEN);\n      };\n      this._element.classList.add(CLASS_NAME_SHOWING);\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n    dispose() {\n      this._clearTimeout();\n      if (this.isShown()) {\n        this._element.classList.remove(CLASS_NAME_SHOW);\n      }\n      super.dispose();\n    }\n    isShown() {\n      return this._element.classList.contains(CLASS_NAME_SHOW);\n    }\n\n    // Private\n\n    _maybeScheduleHide() {\n      if (!this._config.autohide) {\n        return;\n      }\n      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n        return;\n      }\n      this._timeout = setTimeout(() => {\n        this.hide();\n      }, this._config.delay);\n    }\n    _onInteraction(event, isInteracting) {\n      switch (event.type) {\n        case 'mouseover':\n        case 'mouseout':\n          {\n            this._hasMouseInteraction = isInteracting;\n            break;\n          }\n        case 'focusin':\n        case 'focusout':\n          {\n            this._hasKeyboardInteraction = isInteracting;\n            break;\n          }\n      }\n      if (isInteracting) {\n        this._clearTimeout();\n        return;\n      }\n      const nextElement = event.relatedTarget;\n      if (this._element === nextElement || this._element.contains(nextElement)) {\n        return;\n      }\n      this._maybeScheduleHide();\n    }\n    _setListeners() {\n      EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));\n      EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));\n      EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));\n      EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));\n    }\n    _clearTimeout() {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    }\n\n    // Static\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Toast.getOrCreateInstance(this, config);\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n          data[config](this);\n        }\n      });\n    }\n  }\n\n  /**\n   * Data API implementation\n   */\n\n  componentFunctions_js.enableDismissTrigger(Toast);\n\n  /**\n   * jQuery\n   */\n\n  index_js.defineJQueryPlugin(Toast);\n\n  return Toast;\n\n}));\n//# sourceMappingURL=toast.js.map\n", "import { compensateScrollbar, getScrollingElement } from \"@web/core/utils/scrolling\";\n\n/**\n * The bootstrap library extensions and fixes should be done here to avoid\n * patching in place.\n */\n\n/**\n * Review Bootstrap Sanitization: leave it enabled by default but extend it to\n * accept more common tag names like tables and buttons, and common attributes\n * such as style or data-. If a specific tooltip or popover must accept custom\n * tags or attributes, they must be supplied through the whitelist BS\n * parameter explicitely.\n *\n * We cannot disable sanitization because bootstrap uses tooltip/popover\n * DOM attributes in an \"unsafe\" way.\n */\nconst bsSanitizeAllowList = Tooltip.Default.allowList;\n\nbsSanitizeAllowList[\"*\"].push(\"title\", \"style\", /^data-[\\w-]+/);\n\nbsSanitizeAllowList.header = [];\nbsSanitizeAllowList.main = [];\nbsSanitizeAllowList.footer = [];\n\nbsSanitizeAllowList.caption = [];\nbsSanitizeAllowList.col = [\"span\"];\nbsSanitizeAllowList.colgroup = [\"span\"];\nbsSanitizeAllowList.table = [];\nbsSanitizeAllowList.thead = [];\nbsSanitizeAllowList.tbody = [];\nbsSanitizeAllowList.tfooter = [];\nbsSanitizeAllowList.tr = [];\nbsSanitizeAllowList.th = [\"colspan\", \"rowspan\"];\nbsSanitizeAllowList.td = [\"colspan\", \"rowspan\"];\n\nbsSanitizeAllowList.address = [];\nbsSanitizeAllowList.article = [];\nbsSanitizeAllowList.aside = [];\nbsSanitizeAllowList.blockquote = [];\nbsSanitizeAllowList.section = [];\n\nbsSanitizeAllowList.button = [\"type\"];\nbsSanitizeAllowList.del = [];\n\n/* Bootstrap tooltip defaults overwrite */\nTooltip.Default.placement = \"auto\";\nTooltip.Default.fallbackPlacement = [\"bottom\", \"right\", \"left\", \"top\"];\nTooltip.Default.html = true;\nTooltip.Default.trigger = \"hover\";\nTooltip.Default.container = \"body\";\nTooltip.Default.boundary = \"window\";\nTooltip.Default.delay = { show: 1000, hide: 0 };\n\nconst bootstrapShowFunction = Tooltip.prototype.show;\nTooltip.prototype.show = function () {\n    // Overwrite bootstrap tooltip method to prevent showing 2 tooltip at the\n    // same time\n    document.querySelectorAll(\".tooltip\").forEach((el) => el.remove());\n    const errorsToIgnore = [\"Please use show on visible elements\"];\n    try {\n        return bootstrapShowFunction.call(this);\n    } catch (error) {\n        if (errorsToIgnore.includes(error.message)) {\n            return 0;\n        }\n        throw error;\n    }\n};\n\n/**\n * Bootstrap disables dynamic dropdown positioning when it is in a navbar. Here\n * we make this patch to activate this dynamic navbar's dropdown positioning\n * which is useful to avoid that the elements of the website sub-menus overflow\n * the page.\n */\nDropdown.prototype._detectNavbar = function () {\n    return false;\n};\n\n/* Bootstrap modal scrollbar compensation on non-body */\nconst bsAdjustDialogFunction = Modal.prototype._adjustDialog;\nModal.prototype._adjustDialog = function () {\n    const document = this._element.ownerDocument;\n\n    this._scrollBar.reset();\n    document.body.classList.remove(\"modal-open\");\n\n    const scrollable = getScrollingElement(document);\n    if (document.body.contains(scrollable)) {\n        compensateScrollbar(scrollable, true);\n    }\n\n    this._scrollBar.hide();\n    document.body.classList.add(\"modal-open\");\n\n    return bsAdjustDialogFunction.apply(this, arguments);\n};\n\nconst bsResetAdjustmentsFunction = Modal.prototype._resetAdjustments;\nModal.prototype._resetAdjustments = function () {\n    const document = this._element.ownerDocument;\n\n    this._scrollBar.reset();\n    document.body.classList.remove(\"modal-open\");\n\n    const scrollable = getScrollingElement(document);\n    if (document.body.contains(scrollable)) {\n        compensateScrollbar(scrollable, false);\n    }\n    return bsResetAdjustmentsFunction.apply(this, arguments);\n};\n", "/**\n * The jquery library extensions and fixes should be done here to avoid patching\n * in place.\n */\n\n// jQuery selectors extensions\n$.extend($.expr[':'], {\n    data: function (element, index, matches) {\n        return $(element).data(matches[3]);\n    },\n});\n\n// jQuery functions extensions\n$.fn.extend({\n    /**\n     * Makes DOM elements bounce the way Odoo decided it.\n     *\n     * @param {string} [extraClass]\n     */\n    odooBounce: function (extraClass) {\n        for (const el of this) {\n            el.classList.add('o_catch_attention', extraClass);\n            setTimeout(() => el.classList.remove('o_catch_attention', extraClass), 400);\n        }\n        return this;\n    },\n    /**\n     * Allows to bind events to a handler just as the standard `$.on` function\n     * but binds the handler so that it is executed before any already-attached\n     * handler for the same events.\n     *\n     * @see jQuery.on\n     */\n    prependEvent: function (events, selector, data, handler) {\n        this.on.apply(this, arguments);\n\n        events = events.split(' ');\n        return this.each(function () {\n            var el = this;\n            events.forEach((evNameNamespaced) => {\n                var evName = evNameNamespaced.split('.')[0];\n                var handler = $._data(el, 'events')[evName].pop();\n                $._data(el, 'events')[evName].unshift(handler);\n            });\n        });\n    },\n    /**\n     * @deprecated this will soon be removed: just rely on the fact that the\n     * scrollbar is at its natural position.\n     * @returns {jQuery}\n     */\n    getScrollingElement(document = window.document) {\n        const $baseScrollingElement = $(document.scrollingElement);\n        if ($baseScrollingElement.isScrollable()\n                && $baseScrollingElement.hasScrollableContent()) {\n            return $baseScrollingElement;\n        }\n        const bodyHeight = $(document.body).height();\n        for (const el of document.body.children) {\n            // Search for a body child which is at least as tall as the body\n            // and which has the ability to scroll if enough content in it. If\n            // found, suppose this is the top scrolling element.\n            if (bodyHeight - el.scrollHeight > 1.5) {\n                continue;\n            }\n            const $el = $(el);\n            if ($el.isScrollable()) {\n                return $el;\n            }\n        }\n        return $baseScrollingElement;\n    },\n    /**\n     * @deprecated this will soon be removed: just rely on the fact that the\n     * scrollbar is at its natural position.\n     * @returns {jQuery}\n     */\n    getScrollingTarget(contextItem = window.document) {\n        // Cannot use `instanceof` because of cross-frame issues.\n        const isElement = obj => obj && obj.nodeType === Node.ELEMENT_NODE;\n        const isJQuery = obj => obj && ('jquery' in obj);\n\n        const $scrollingElement = isElement(contextItem)\n            ? $(contextItem)\n            : isJQuery(contextItem)\n            ? contextItem\n            : $().getScrollingElement(contextItem);\n        const document = $scrollingElement[0].ownerDocument;\n        return $scrollingElement.is(document.scrollingElement)\n            ? $(document.defaultView)\n            : $scrollingElement;\n    },\n    /**\n     * @return {boolean}\n     */\n    hasScrollableContent() {\n        return this[0].scrollHeight > this[0].clientHeight;\n    },\n    /**\n     * @returns {boolean}\n     */\n    isScrollable() {\n        if (!this.length) {\n            return false;\n        }\n        const overflow = this.css('overflow-y');\n        const el = this[0];\n        return overflow === 'auto' || overflow === 'scroll'\n            || (overflow === 'visible' && el === el.ownerDocument.scrollingElement);\n    },\n});\n\n// jQuery functions monkey-patching\n\n// Some magic to ensure scrollTop and animate on html/body animate the top level\n// scrollable element even if not html or body. Note: we should consider\n// removing this as it was only really needed when the #wrapwrap was the one\n// with the scrollbar. Although the rest of the code still use\n// getScrollingElement to be generic so this is consistent. Maybe all of this\n// can live on as long as we continue using jQuery a lot. We can decide of the\n// fate of getScrollingElement and related code the moment we get rid of jQuery.\nconst originalScrollTop = $.fn.scrollTop;\n$.fn.scrollTop = function (value) {\n    if (value !== undefined && this.filter('html, body').length) {\n        // The caller wants to scroll a set of elements including html and/or\n        // body to a specific point -> do that but make sure to add the real\n        // top level element to that set of elements if any different is found.\n        const $withRealScrollable = this.not('html, body').add($().getScrollingElement(this[0].ownerDocument));\n        originalScrollTop.apply($withRealScrollable, arguments);\n        return this;\n    } else if (value === undefined && this.eq(0).is('html, body')) {\n        // The caller wants to get the scroll point of a set of elements, jQuery\n        // will return the scroll point of the first one, if it is html or body\n        // return the scroll point of the real top level element.\n        return originalScrollTop.apply($().getScrollingElement(this[0].ownerDocument), arguments);\n    }\n    return originalScrollTop.apply(this, arguments);\n};\nconst originalAnimate = $.fn.animate;\n$.fn.animate = function (properties, ...rest) {\n    const props = Object.assign({}, properties);\n    if ('scrollTop' in props && this.filter('html, body').length) {\n        // The caller wants to scroll a set of elements including html and/or\n        // body to a specific point -> do that but make sure to add the real\n        // top level element to that set of elements if any different is found.\n        const $withRealScrollable = this.not('html, body').add($().getScrollingElement(this[0].ownerDocument));\n        originalAnimate.call($withRealScrollable, {'scrollTop': props['scrollTop']}, ...rest);\n        delete props['scrollTop'];\n    }\n    if (!Object.keys(props).length) {\n        return this;\n    }\n    return originalAnimate.call(this, props, ...rest);\n};\n", "/**\n * Improved John Resig's inheritance, based on:\n *\n * Simple JavaScript Inheritance\n * By John Resig http://ejohn.org/\n * MIT Licensed.\n *\n * Adds \"include()\"\n *\n * Defines The Class object. That object can be used to define and inherit classes using\n * the extend() method.\n *\n * Example::\n *\n *     var Person = Class.extend({\n *      init: function(isDancing){\n *         this.dancing = isDancing;\n *       },\n *       dance: function(){\n *         return this.dancing;\n *       }\n *     });\n *\n * The init() method act as a constructor. This class can be instanced this way::\n *\n *     var person = new Person(true);\n *     person.dance();\n *\n *     The Person class can also be extended again:\n *\n *     var Ninja = Person.extend({\n *       init: function(){\n *         this._super( false );\n *       },\n *       dance: function(){\n *         // Call the inherited version of dance()\n *         return this._super();\n *       },\n *       swingSword: function(){\n *         return true;\n *       }\n *     });\n *\n * When extending a class, each re-defined method can use this._super() to call the previous\n * implementation of that method.\n *\n * @class Class\n */\nfunction OdooClass(){}\n\nvar initializing = false;\n// eslint-disable-next-line no-undef\nvar fnTest = /xyz/.test(function(){xyz();}) ? /\\b_super\\b/ : /.*/;\n\n/**\n * Subclass an existing class\n *\n * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class\n */\nOdooClass.extend = function() {\n    var _super = this.prototype;\n    // Support mixins arguments\n    var args = [...arguments];\n    args.unshift({});\n\n    const prop = {};\n    args.forEach((arg) => {\n        Object.assign(prop, arg);\n    });\n\n    // Instantiate a web class (but only create the instance,\n    // don't run the init constructor)\n    initializing = true;\n    var This = this;\n    var prototype = new This();\n    initializing = false;\n\n    // Copy the properties over onto the new prototype\n    Object.keys(prop).forEach((name) => {\n        // Check if we're overwriting an existing function\n        prototype[name] = typeof prop[name] == \"function\" &&\n                          fnTest.test(prop[name]) ?\n                (function(name, fn) {\n                    return function() {\n                        var tmp = this._super;\n\n                        // Add a new ._super() method that is the same\n                        // method but on the super-class\n                        this._super = _super[name];\n\n                        // The method only need to be bound temporarily, so\n                        // we remove it when we're done executing\n                        var ret = fn.apply(this, arguments);\n                        this._super = tmp;\n\n                        return ret;\n                    };\n                })(name, prop[name]) :\n                prop[name];\n    });\n\n    // The dummy class constructor\n    function Class() {\n        if(this.constructor !== OdooClass){\n            throw new Error(\"You can only instanciate objects with the 'new' operator\");\n        }\n        // All construction is actually done in the init method\n        this._super = null;\n        if (!initializing && this.init) {\n            var ret = this.init.apply(this, arguments);\n            if (ret) { return ret; }\n        }\n        return this;\n    }\n    Class.include = function (properties) {\n        Object.keys(properties).forEach((name) => {\n            if (typeof properties[name] !== 'function'\n                    || !fnTest.test(properties[name])) {\n                prototype[name] = properties[name];\n            } else if (typeof prototype[name] === 'function'\n                       && prototype.hasOwnProperty(name)) {\n                prototype[name] = (function (name, fn, previous) {\n                    return function () {\n                        var tmp = this._super;\n                        this._super = previous;\n                        var ret = fn.apply(this, arguments);\n                        this._super = tmp;\n                        return ret;\n                    };\n                })(name, properties[name], prototype[name]);\n            } else if (typeof _super[name] === 'function') {\n                prototype[name] = (function (name, fn) {\n                    return function () {\n                        var tmp = this._super;\n                        this._super = _super[name];\n                        var ret = fn.apply(this, arguments);\n                        this._super = tmp;\n                        return ret;\n                    };\n                })(name, properties[name]);\n            }\n        });\n    };\n\n    // Populate our constructed prototype object\n    Class.prototype = prototype;\n\n    // Enforce the constructor to be what we expect\n    Class.constructor = Class;\n\n    // And make this class extendable\n    Class.extend = this.extend;\n\n    return Class;\n};\n\nexport default OdooClass;\n", "import { App, EventBus } from \"@odoo/owl\";\nimport { SERVICES_METADATA } from \"@web/core/utils/hooks\";\nimport { registry } from \"@web/core/registry\";\nimport { getTemplate } from \"@web/core/templates\";\nimport { appTranslateFn } from \"@web/core/l10n/translation\";\nimport { session } from \"@web/session\";\nimport { isMacOS } from \"@web/core/browser/feature_detection\";\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef {Object} OdooEnv\n * @property {import(\"services\").Services} services\n * @property {EventBus} bus\n * @property {string} debug\n * @property {boolean} [isSmall]\n */\n\n// -----------------------------------------------------------------------------\n// makeEnv\n// -----------------------------------------------------------------------------\n\n/**\n * Return a value Odoo Env object\n *\n * @returns {OdooEnv}\n */\nexport function makeEnv() {\n    const bus = new EventBus();\n    const prom = new Promise((resolve) => {\n        bus.addEventListener(\"SERVICES-LOADED\", resolve, { once: true });\n    });\n    return {\n        bus,\n        isReady: prom,\n        services: {},\n        debug: odoo.debug,\n        get isSmall() {\n            throw new Error(\"UI service not initialized!\");\n        },\n    };\n}\n\n// -----------------------------------------------------------------------------\n// Service Launcher\n// -----------------------------------------------------------------------------\n\nconst serviceRegistry = registry.category(\"services\");\n\nserviceRegistry.addValidation({\n    start: Function,\n    dependencies: { type: Array, element: String, optional: true },\n    async: { type: [{ type: Array, element: String }, { value: true }], optional: true },\n    \"*\": true,\n});\n\nlet startServicesPromise = null;\n\n/**\n * Start all services registered in the service registry, while making sure\n * each service dependencies are properly fulfilled.\n *\n * @param {OdooEnv} env\n * @returns {Promise<void>}\n */\nexport async function startServices(env) {\n    // Wait for all synchronous code so that if new services that depend on\n    // one another are added to the registry, they're all present before we\n    // start them regardless of the order they're added to the registry.\n    await Promise.resolve();\n\n    const toStart = new Map();\n    serviceRegistry.addEventListener(\"UPDATE\", async (ev) => {\n        // Wait for all synchronous code so that if new services that depend on\n        // one another are added to the registry, they're all present before we\n        // start them regardless of the order they're added to the registry.\n        await Promise.resolve();\n        const { operation, key: name, value: service } = ev.detail;\n        if (operation === \"delete\") {\n            // We hardly see why it would be usefull to remove a service.\n            // Furthermore we could encounter problems with dependencies.\n            // Keep it simple!\n            return;\n        }\n        if (toStart.size) {\n            const namedService = Object.assign(Object.create(service), { name });\n            toStart.set(name, namedService);\n        } else {\n            await _startServices(env, toStart);\n        }\n    });\n    await _startServices(env, toStart);\n}\n\nasync function _startServices(env, toStart) {\n    if (startServicesPromise) {\n        return startServicesPromise.then(() => _startServices(env, toStart));\n    }\n    const services = env.services;\n    for (const [name, service] of serviceRegistry.getEntries()) {\n        if (!(name in services)) {\n            const namedService = Object.assign(Object.create(service), { name });\n            toStart.set(name, namedService);\n        }\n    }\n\n    // start as many services in parallel as possible\n    async function start() {\n        let service = null;\n        const proms = [];\n        while ((service = findNext())) {\n            const name = service.name;\n            toStart.delete(name);\n            const entries = (service.dependencies || []).map((dep) => [dep, services[dep]]);\n            const dependencies = Object.fromEntries(entries);\n            if (name in services) {\n                continue;\n            }\n            const value = service.start(env, dependencies);\n            if (\"async\" in service) {\n                SERVICES_METADATA[name] = service.async;\n            }\n            proms.push(\n                Promise.resolve(value).then((val) => {\n                    services[name] = val || null;\n                })\n            );\n        }\n        await Promise.all(proms);\n        if (proms.length) {\n            return start();\n        }\n    }\n    startServicesPromise = start().finally(() => {\n        startServicesPromise = null;\n    });\n    await startServicesPromise;\n    env.bus.trigger(\"SERVICES-LOADED\");\n    if (toStart.size) {\n        const missingDeps = new Set();\n        for (const service of toStart.values()) {\n            for (const dependency of service.dependencies) {\n                if (!(dependency in services) && !toStart.has(dependency)) {\n                    missingDeps.add(dependency);\n                }\n            }\n        }\n        const depNames = [...missingDeps].join(\", \");\n        throw new Error(\n            `Some services could not be started: ${[\n                ...toStart.keys(),\n            ]}. Missing dependencies: ${depNames}`\n        );\n    }\n\n    function findNext() {\n        for (const s of toStart.values()) {\n            if (s.dependencies) {\n                if (s.dependencies.every((d) => d in services)) {\n                    return s;\n                }\n            } else {\n                return s;\n            }\n        }\n        return null;\n    }\n}\n\nexport const customDirectives = {\n    // t-custom-click=\"handler\"\n    // This custom directive will add two even listeners (\"click\"; \"auxclick\") and call the global value \"click\".\n    // The global value \"click\" will call the handler with two parameters :\n    //      - ev (the original event)\n    //      - isMiddleClick (a boolean that says if the user middle clicked, or if he did a ctrl+click)\n    //\n    click: (node, value, modifiers) => {\n        let mods = \"\";\n        if (modifiers.includes(\"synthetic\")) {\n            mods += \".synthetic\";\n        }\n        if (modifiers.includes(\"capture\")) {\n            mods += \".capture\";\n        }\n        const handlerFunction = `(ev) => __globals__.click(ev, (${value}).bind(this), '${JSON.stringify(\n            modifiers\n        )}')`;\n        node.setAttribute(`t-on-click${mods}`, handlerFunction);\n        node.setAttribute(`t-on-auxclick${mods}`, handlerFunction);\n    },\n};\n\nexport const globalValues = {\n    click: (ev, value, modifiers) => {\n        if (ev.button === 0 || ev.button === 1) {\n            modifiers = JSON.parse(modifiers);\n            for (const modifier of modifiers) {\n                if (modifier === \"stop\") {\n                    ev.stopPropagation();\n                }\n                if (modifier === \"prevent\") {\n                    ev.preventDefault();\n                }\n            }\n            const ctrlKey = isMacOS() ? ev.metaKey : ev.ctrlKey;\n            const isMiddleClick = (ctrlKey && ev.button === 0) || ev.button === 1;\n            value(ev, isMiddleClick);\n        }\n    },\n};\n\n/**\n * Create an application with a given component as root and mount it. If no env\n * is provided, the application will be treated as a \"root\": an env will be\n * created and the services will be started, it will also be set as the root\n * in `__WOWL_DEBUG__`\n *\n * @param {import(\"@odoo/owl\").Component} component the component to mount\n * @param {HTMLElement} target the HTML element in which to mount the app\n * @param {Partial<ConstructorParameters<typeof App>[1]>} [appConfig] object\n *  containing a (partial) config for the app.\n */\nexport async function mountComponent(component, target, appConfig = {}) {\n    let { env } = appConfig;\n    const isRoot = !env;\n    if (isRoot) {\n        env = await makeEnv();\n        await startServices(env);\n    }\n    const app = new App(component, {\n        env,\n        getTemplate,\n        dev: env.debug || session.test_mode,\n        warnIfNoStaticProps: !session.test_mode,\n        name: component.constructor.name,\n        translatableAttributes: [\"data-tooltip\"],\n        translateFn: appTranslateFn,\n        customDirectives,\n        globalValues,\n        ...appConfig,\n    });\n    const root = await app.mount(target);\n    if (isRoot) {\n        odoo.__WOWL_DEBUG__ = { root };\n    }\n    return app;\n}\n", "import { browser } from \"@web/core/browser/browser\";\nimport { localization } from \"@web/core/l10n/localization\";\nimport { clamp } from \"@web/core/utils/numbers\";\n\nimport { Component, onMounted, onWillUnmount, useRef, useState } from \"@odoo/owl\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\n\nconst isScrollSwipable = (scrollables) => {\n    return {\n        left: !scrollables.filter((e) => e.scrollLeft !== 0).length,\n        right: !scrollables.filter(\n            (e) => e.scrollLeft + Math.round(e.getBoundingClientRect().width) !== e.scrollWidth\n        ).length,\n    };\n};\n\n/**\n * Action Swiper\n *\n * This component is intended to perform action once a user has completed a touch swipe.\n * You can choose the direction allowed for such behavior (left, right or both).\n * The action to perform must be passed as a props. It is possible to define a condition\n * to allow the swipe interaction conditionnally.\n * @extends Component\n */\nexport class ActionSwiper extends Component {\n    static template = \"web.ActionSwiper\";\n    static props = {\n        onLeftSwipe: {\n            type: Object,\n            args: {\n                action: Function,\n                icon: String,\n                bgColor: String,\n            },\n            optional: true,\n        },\n        onRightSwipe: {\n            type: Object,\n            args: {\n                action: Function,\n                icon: String,\n                bgColor: String,\n            },\n            optional: true,\n        },\n        slots: Object,\n        animationOnMove: { type: Boolean, optional: true },\n        animationType: { type: String, optional: true },\n        swipeDistanceRatio: { type: Number, optional: true },\n        swipeInvalid: { type: Function, optional: true },\n    };\n\n    static defaultProps = {\n        onLeftSwipe: undefined,\n        onRightSwipe: undefined,\n        animationOnMove: true,\n        animationType: \"bounce\",\n        swipeDistanceRatio: 2,\n    };\n\n    setup() {\n        this.actionTimeoutId = null;\n        this.resetTimeoutId = null;\n        this.defaultState = {\n            containerStyle: \"\",\n            isSwiping: false,\n            width: undefined,\n        };\n        this.root = useRef(\"root\");\n        this.targetContainer = useRef(\"targetContainer\");\n        this.state = useState({ ...this.defaultState });\n        this.scrollables = undefined;\n        this.startX = undefined;\n        this.swipedDistance = 0;\n        this.isScrollValidated = false;\n        onMounted(() => {\n            if (this.targetContainer.el) {\n                this.state.width = this.targetContainer.el.getBoundingClientRect().width;\n            }\n            // Forward classes set on component to slot, as we only want to wrap an\n            // existing component without altering the DOM structure any more than\n            // strictly necessary\n            if (this.props.onLeftSwipe || this.props.onRightSwipe) {\n                const classes = new Set(this.root.el.classList);\n                classes.delete(\"o_actionswiper\");\n                for (const className of classes) {\n                    this.targetContainer.el.firstChild.classList.add(className);\n                    this.root.el.classList.remove(className);\n                }\n            }\n        });\n        onWillUnmount(() => {\n            browser.clearTimeout(this.actionTimeoutId);\n            browser.clearTimeout(this.resetTimeoutId);\n        });\n    }\n    get localizedProps() {\n        return {\n            onLeftSwipe:\n                localization.direction === \"rtl\" ? this.props.onRightSwipe : this.props.onLeftSwipe,\n            onRightSwipe:\n                localization.direction === \"rtl\" ? this.props.onLeftSwipe : this.props.onRightSwipe,\n        };\n    }\n\n    /**\n     * @private\n     * @param {TouchEvent} ev\n     */\n    _onTouchEndSwipe() {\n        if (this.state.isSwiping) {\n            this.state.isSwiping = false;\n            if (\n                this.localizedProps.onRightSwipe &&\n                this.swipedDistance > this.state.width / this.props.swipeDistanceRatio\n            ) {\n                this.swipedDistance = this.state.width;\n                this.handleSwipe(this.localizedProps.onRightSwipe.action);\n            } else if (\n                this.localizedProps.onLeftSwipe &&\n                this.swipedDistance < -this.state.width / this.props.swipeDistanceRatio\n            ) {\n                this.swipedDistance = -this.state.width;\n                this.handleSwipe(this.localizedProps.onLeftSwipe.action);\n            } else {\n                this.state.containerStyle = \"\";\n            }\n        }\n    }\n    /**\n     * @private\n     * @param {TouchEvent} ev\n     */\n    _onTouchMoveSwipe(ev) {\n        if (this.state.isSwiping) {\n            if (this.props.swipeInvalid && this.props.swipeInvalid()) {\n                this.state.isSwiping = false;\n                return;\n            }\n            const { onLeftSwipe, onRightSwipe } = this.localizedProps;\n            this.swipedDistance = clamp(\n                ev.touches[0].clientX - this.startX,\n                onLeftSwipe ? -this.state.width : 0,\n                onRightSwipe ? this.state.width : 0\n            );\n            // Prevent the browser to navigate back/forward when using swipe\n            // gestures while still allowing to scroll vertically.\n            if (Math.abs(this.swipedDistance) > 40) {\n                ev.preventDefault();\n            }\n            // If there are scrollable elements under touch pressure,\n            // they must be at their limits to allow swiping.\n            if (\n                !this.isScrollValidated &&\n                this.scrollables &&\n                !isScrollSwipable(this.scrollables)[this.swipedDistance > 0 ? \"left\" : \"right\"]\n            ) {\n                return this._reset();\n            }\n            this.isScrollValidated = true;\n\n            if (this.props.animationOnMove) {\n                this.state.containerStyle = `transform: translateX(${this.swipedDistance}px)`;\n            }\n        }\n    }\n    /**\n     * @private\n     * @param {TouchEvent} ev\n     */\n    _onTouchStartSwipe(ev) {\n        this.scrollables = ev\n            .composedPath()\n            .filter(\n                (e) =>\n                    e.nodeType === 1 &&\n                    this.targetContainer.el.contains(e) &&\n                    e.scrollWidth > e.getBoundingClientRect().width &&\n                    [\"auto\", \"scroll\"].includes(window.getComputedStyle(e)[\"overflow-x\"])\n            );\n        if (!this.state.width) {\n            this.state.width =\n                this.targetContainer && this.targetContainer.el.getBoundingClientRect().width;\n        }\n        this.state.isSwiping = true;\n        this.isScrollValidated = false;\n        this.startX = ev.touches[0].clientX;\n    }\n\n    /**\n     * @private\n     */\n    _reset() {\n        Object.assign(this.state, { ...this.defaultState });\n        this.scrollables = undefined;\n        this.startX = undefined;\n        this.swipedDistance = 0;\n        this.isScrollValidated = false;\n    }\n\n    handleSwipe(action) {\n        if (this.props.animationType === \"bounce\") {\n            this.state.containerStyle = `transform: translateX(${this.swipedDistance}px)`;\n            this.actionTimeoutId = browser.setTimeout(async () => {\n                await action(Promise.resolve());\n                this._reset();\n            }, 500);\n        } else if (this.props.animationType === \"forwards\") {\n            this.state.containerStyle = `transform: translateX(${this.swipedDistance}px)`;\n            this.actionTimeoutId = browser.setTimeout(async () => {\n                const prom = new Deferred();\n                await action(prom);\n                this.state.isSwiping = true;\n                this.state.containerStyle = `transform: translateX(${-this.swipedDistance}px)`;\n                this.resetTimeoutId = browser.setTimeout(() => {\n                    prom.resolve();\n                    this._reset();\n                }, 100);\n            }, 100);\n        } else {\n            return action(Promise.resolve());\n        }\n    }\n}\n", "import { browser } from \"./browser/browser\";\n\nbrowser.addEventListener(\"click\", (ev) => {\n    const href = ev.target.closest(\"a\")?.getAttribute(\"href\");\n    if (href && href === \"#\") {\n        ev.preventDefault(); // single hash in href are just a way to activate A-tags node\n        return;\n    }\n});\n", "import { Component, onWillStart, whenReady, xml } from \"@odoo/owl\";\nimport { session } from \"@web/session\";\nimport { registry } from \"./registry\";\n\n/**\n * @typedef {{\n *  cssLibs: string[];\n *  jsLibs: string[];\n * }} BundleFileNames\n */\n\nexport const globalBundleCache = new Map();\nexport const assetCacheByDocument = new WeakMap();\n\nfunction getGlobalBundleCache() {\n    return globalBundleCache;\n}\n\nfunction getAssetCache(targetDoc) {\n    if (!assetCacheByDocument.has(targetDoc)) {\n        assetCacheByDocument.set(targetDoc, new Map());\n    }\n    return assetCacheByDocument.get(targetDoc);\n}\n\nexport function computeBundleCacheMap(targetDoc) {\n    const cacheMap = getGlobalBundleCache();\n    for (const script of targetDoc.head.querySelectorAll(\"script[src]\")) {\n        cacheMap.set(script.getAttribute(\"src\"), Promise.resolve());\n    }\n    for (const link of targetDoc.head.querySelectorAll(\"link[rel=stylesheet][href]\")) {\n        cacheMap.set(link.getAttribute(\"href\"), Promise.resolve());\n    }\n}\n\nwhenReady(() => computeBundleCacheMap(document));\n\n/**\n * @param {HTMLLinkElement | HTMLScriptElement} el\n * @param {(event: Event) => any} onLoad\n * @param {(error: Error) => any} onError\n */\nconst onLoadAndError = (el, onLoad, onError) => {\n    const onLoadListener = (event) => {\n        removeListeners();\n        onLoad(event);\n    };\n\n    const onErrorListener = (error) => {\n        removeListeners();\n        onError(error);\n    };\n\n    const removeListeners = () => {\n        el.removeEventListener(\"load\", onLoadListener);\n        el.removeEventListener(\"error\", onErrorListener);\n    };\n\n    el.addEventListener(\"load\", onLoadListener);\n    el.addEventListener(\"error\", onErrorListener);\n\n    window.addEventListener(\"pagehide\", () => {\n        removeListeners();\n    });\n};\n\n/** @type {typeof assets[\"getBundle\"]} */\nexport function getBundle() {\n    return assets.getBundle(...arguments);\n}\n\n/** @type {typeof assets[\"loadBundle\"]} */\nexport function loadBundle() {\n    return assets.loadBundle(...arguments);\n}\n\n/** @type {typeof assets[\"loadJS\"]} */\nexport function loadJS() {\n    return assets.loadJS(...arguments);\n}\n\n/** @type {typeof assets[\"loadCSS\"]} */\nexport function loadCSS() {\n    return assets.loadCSS(...arguments);\n}\n\nexport class AssetsLoadingError extends Error {}\n\n/**\n * Utility component that loads an asset bundle before instanciating a component\n */\nexport class LazyComponent extends Component {\n    static template = xml`<t t-component=\"Component\" t-props=\"componentProps\"/>`;\n    static props = {\n        Component: String,\n        bundle: String,\n        props: { type: [Object, Function], optional: true },\n    };\n    setup() {\n        onWillStart(async () => {\n            await loadBundle(this.props.bundle);\n            this.Component = registry.category(\"lazy_components\").get(this.props.Component);\n        });\n    }\n\n    get componentProps() {\n        return typeof this.props.props === \"function\" ? this.props.props() : this.props.props;\n    }\n}\n\n/**\n * This export is done only in order to modify the behavior of the exported\n * functions. This is done in order to be able to make a test environment.\n * Modules should only use the methods exported below.\n */\nexport const assets = {\n    retries: {\n        count: 3,\n        delay: 5000,\n        extraDelay: 2500,\n    },\n\n    /**\n     * Get the files information as descriptor object from a public asset template.\n     *\n     * @param {string} bundleName Name of the bundle containing the list of files\n     * @returns {Promise<BundleFileNames>}\n     */\n    getBundle(bundleName) {\n        const cacheMap = getGlobalBundleCache();\n        if (cacheMap.has(bundleName)) {\n            return cacheMap.get(bundleName);\n        }\n        const url = new URL(`/web/bundle/${bundleName}`, location.origin);\n        for (const [key, value] of Object.entries(session.bundle_params || {})) {\n            url.searchParams.set(key, value);\n        }\n        const promise = fetch(url)\n            .then(async (response) => {\n                const cssLibs = [];\n                const jsLibs = [];\n                if (!response.bodyUsed) {\n                    const result = await response.json();\n                    for (const { src, type } of Object.values(result)) {\n                        if (type === \"link\" && src) {\n                            cssLibs.push(src);\n                        } else if (type === \"script\" && src) {\n                            jsLibs.push(src);\n                        }\n                    }\n                }\n                return { cssLibs, jsLibs };\n            })\n            .catch((reason) => {\n                cacheMap.delete(bundleName);\n                throw new AssetsLoadingError(`The loading of ${url} failed`, { cause: reason });\n            });\n        cacheMap.set(bundleName, promise);\n        return promise;\n    },\n\n    /**\n     * Loads the given js/css libraries and asset bundles. Note that no library or\n     * asset will be loaded if it was already done before.\n     *\n     * @param {string} bundleName\n     * @param {Object} options\n     * @param {Document} [options.targetDoc=document] document to which the bundle will be applied (e.g. iframe document)\n     * @param {Boolean} [options.css=true] apply bundle css on targetDoc\n     * @param {Boolean} [options.js=true] apply bundle js on targetDoc\n     * @returns {Promise<void[]>}\n     */\n    loadBundle(bundleName, { targetDoc = document, css = true, js = true } = {}) {\n        if (typeof bundleName !== \"string\") {\n            throw new Error(\n                `loadBundle(bundleName:string) accepts only bundleName argument as a string ! Not ${JSON.stringify(\n                    bundleName\n                )} as ${typeof bundleName}`\n            );\n        }\n        return getBundle(bundleName).then(({ cssLibs, jsLibs }) => {\n            const promises = [];\n            if (css && cssLibs) {\n                promises.push(...cssLibs.map((url) => assets.loadCSS(url, { targetDoc })));\n            }\n            if (js && jsLibs) {\n                promises.push(...jsLibs.map((url) => assets.loadJS(url, { targetDoc })));\n            }\n            return Promise.all(promises);\n        });\n    },\n\n    /**\n     * Loads the given url as a stylesheet.\n     *\n     * @param {string} url the url of the stylesheet\n     * @param {number} [retryCount]\n     * @param {Object} options\n     * @param {number} [retryCount]\n     * @param {Document} [options.targetDoc=document] document to which the bundle will be applied (e.g. iframe document)\n     * @returns {Promise<void>} resolved when the stylesheet has been loaded\n     */\n    loadCSS(url, { retryCount = 0, targetDoc = document } = {}) {\n        const cacheMap = getAssetCache(targetDoc);\n        if (cacheMap.has(url)) {\n            return cacheMap.get(url);\n        }\n        const linkEl = targetDoc.createElement(\"link\");\n        linkEl.setAttribute(\"href\", url);\n        linkEl.type = \"text/css\";\n        linkEl.rel = \"stylesheet\";\n        const promise = new Promise((resolve, reject) =>\n            onLoadAndError(linkEl, resolve, async (error) => {\n                cacheMap.delete(url);\n                if (retryCount < assets.retries.count) {\n                    const delay = assets.retries.delay + assets.retries.extraDelay * retryCount;\n                    await new Promise((res) => setTimeout(res, delay));\n                    linkEl.remove();\n                    loadCSS(url, { retryCount: retryCount + 1, targetDoc })\n                        .then(resolve)\n                        .catch((reason) => {\n                            cacheMap.delete(url);\n                            reject(reason);\n                        });\n                } else {\n                    reject(\n                        new AssetsLoadingError(`The loading of ${url} failed`, { cause: error })\n                    );\n                }\n            })\n        );\n        cacheMap.set(url, promise);\n        targetDoc.head.appendChild(linkEl);\n        return promise;\n    },\n\n    /**\n     * Loads the given url inside a script tag.\n     *\n     * @param {string} url the url of the script\n     * @param {Document} targetDoc document to which the bundle will be applied (e.g. iframe document)\n     * @returns {Promise<void>} resolved when the script has been loaded\n     */\n    loadJS(url, { targetDoc = document } = {}) {\n        const cacheMap = getAssetCache(targetDoc);\n        if (cacheMap.has(url)) {\n            return cacheMap.get(url);\n        }\n        const scriptEl = targetDoc.createElement(\"script\");\n        scriptEl.setAttribute(\"src\", url);\n        scriptEl.type = url.includes(\"web/static/lib/pdfjs/\") ? \"module\" : \"text/javascript\";\n        const promise = new Promise((resolve, reject) =>\n            onLoadAndError(scriptEl, resolve, (error) => {\n                cacheMap.delete(url);\n                reject(new AssetsLoadingError(`The loading of ${url} failed`, { cause: error }));\n            })\n        );\n        cacheMap.set(url, promise);\n        targetDoc.head.appendChild(scriptEl);\n        return promise;\n    },\n};\n", "import { Deferred } from \"@web/core/utils/concurrency\";\nimport { useAutofocus, useForwardRefToParent, useService } from \"@web/core/utils/hooks\";\nimport { isScrollableY, scrollTo } from \"@web/core/utils/scrolling\";\nimport { useDebounced } from \"@web/core/utils/timing\";\nimport { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\nimport { usePosition } from \"@web/core/position/position_hook\";\nimport { Component, onWillUpdateProps, useExternalListener, useRef, useState } from \"@odoo/owl\";\nimport { mergeClasses } from \"@web/core/utils/classname\";\n\nexport class AutoComplete extends Component {\n    static template = \"web.AutoComplete\";\n    static props = {\n        value: { type: String, optional: true },\n        id: { type: String, optional: true },\n        sources: {\n            type: Array,\n            element: {\n                type: Object,\n                shape: {\n                    placeholder: { type: String, optional: true },\n                    options: [Array, Function],\n                    optionSlot: { type: String, optional: true },\n                },\n            },\n        },\n        placeholder: { type: String, optional: true },\n        autocomplete: { type: String, optional: true },\n        autoSelect: { type: Boolean, optional: true },\n        resetOnSelect: { type: Boolean, optional: true },\n        onInput: { type: Function, optional: true },\n        onCancel: { type: Function, optional: true },\n        onChange: { type: Function, optional: true },\n        onBlur: { type: Function, optional: true },\n        onFocus: { type: Function, optional: true },\n        searchOnInputClick: { type: Boolean, optional: true },\n        input: { type: Function, optional: true },\n        inputDebounceDelay: { type: Number, optional: true },\n        dropdown: { type: Boolean, optional: true },\n        autofocus: { type: Boolean, optional: true },\n        class: { type: String, optional: true },\n        slots: { type: Object, optional: true },\n        menuPositionOptions: { type: Object, optional: true },\n        menuCssClass: { type: [String, Array, Object], optional: true },\n        selectOnBlur: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        value: \"\",\n        placeholder: \"\",\n        autocomplete: \"new-password\",\n        autoSelect: false,\n        dropdown: true,\n        onInput: () => {},\n        onCancel: () => {},\n        onChange: () => {},\n        onBlur: () => {},\n        onFocus: () => {},\n        searchOnInputClick: true,\n        inputDebounceDelay: 250,\n        menuPositionOptions: {},\n        menuCssClass: {},\n    };\n\n    get timeout() {\n        return this.props.inputDebounceDelay;\n    }\n\n    setup() {\n        this.nextSourceId = 0;\n        this.nextOptionId = 0;\n        this.sources = [];\n        this.inEdition = false;\n        this.mouseSelectionActive = false;\n        this.isOptionSelected = false;\n\n        this.state = useState({\n            navigationRev: 0,\n            optionsRev: 0,\n            open: false,\n            activeSourceOption: null,\n            value: this.props.value,\n        });\n\n        this.inputRef = useForwardRefToParent(\"input\");\n        this.listRef = useRef(\"sourcesList\");\n        if (this.props.autofocus) {\n            useAutofocus({ refName: \"input\" });\n        }\n        this.root = useRef(\"root\");\n\n        this.debouncedProcessInput = useDebounced(async () => {\n            const currentPromise = this.pendingPromise;\n            this.pendingPromise = null;\n            this.props.onInput({\n                inputValue: this.inputRef.el.value,\n            });\n            try {\n                await this.open(true);\n                currentPromise.resolve();\n            } catch {\n                currentPromise.reject();\n            } finally {\n                if (currentPromise === this.loadingPromise) {\n                    this.loadingPromise = null;\n                }\n            }\n        }, this.timeout);\n\n        useExternalListener(window, \"scroll\", this.externalClose, true);\n        useExternalListener(window, \"pointerdown\", this.externalClose, true);\n        useExternalListener(window, \"mousemove\", () => (this.mouseSelectionActive = true), true);\n\n        this.hotkey = useService(\"hotkey\");\n        this.hotkeysToRemove = [];\n\n        onWillUpdateProps((nextProps) => {\n            if (this.props.value !== nextProps.value || this.forceValFromProp) {\n                this.forceValFromProp = false;\n                if (!this.inEdition) {\n                    this.state.value = nextProps.value;\n                    this.inputRef.el.value = nextProps.value;\n                }\n                this.close();\n            }\n        });\n\n        // position and size\n        if (this.props.dropdown) {\n            usePosition(\"sourcesList\", () => this.targetDropdown, this.dropdownOptions);\n        } else {\n            this.open(false);\n        }\n    }\n\n    get targetDropdown() {\n        return this.inputRef.el;\n    }\n\n    get activeSourceOptionId() {\n        if (!this.isOpened || !this.state.activeSourceOption) {\n            return undefined;\n        }\n        const [sourceIndex, optionIndex] = this.state.activeSourceOption;\n        const source = this.sources[sourceIndex];\n        return `${this.props.id || \"autocomplete\"}_${sourceIndex}_${\n            source.isLoading ? \"loading\" : optionIndex\n        }`;\n    }\n\n    get dropdownOptions() {\n        return {\n            position: \"bottom-start\",\n            ...this.props.menuPositionOptions,\n        };\n    }\n\n    get isOpened() {\n        return this.state.open;\n    }\n\n    get hasOptions() {\n        for (const source of this.sources) {\n            if (source.isLoading || source.options.length) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    get activeOption() {\n        if (!this.state.activeSourceOption) {\n            return null;\n        }\n        const [sourceIndex, optionIndex] = this.state.activeSourceOption;\n        return this.sources[sourceIndex].options[optionIndex];\n    }\n\n    open(useInput = false) {\n        this.state.open = true;\n        return this.loadSources(useInput);\n    }\n\n    close() {\n        this.state.open = false;\n        this.state.activeSourceOption = null;\n        this.mouseSelectionActive = false;\n    }\n\n    cancel() {\n        if (this.inputRef.el.value.length) {\n            if (this.props.autoSelect) {\n                this.inputRef.el.value = this.props.value;\n                this.props.onCancel();\n            }\n        }\n        this.close();\n    }\n\n    async loadSources(useInput) {\n        this.sources = [];\n        this.state.activeSourceOption = null;\n        const proms = [];\n        for (const pSource of this.props.sources) {\n            const source = this.makeSource(pSource);\n            this.sources.push(source);\n\n            const options = this.loadOptions(\n                pSource.options,\n                useInput ? this.inputRef.el.value.trim() : \"\"\n            );\n            if (options instanceof Promise) {\n                source.isLoading = true;\n                const prom = options.then((options) => {\n                    source.options = options.map((option) => this.makeOption(option));\n                    source.isLoading = false;\n                    this.state.optionsRev++;\n                });\n                proms.push(prom);\n            } else {\n                source.options = options.map((option) => this.makeOption(option));\n            }\n        }\n\n        await Promise.all(proms);\n        this.navigate(0);\n        this.scroll();\n    }\n    get displayOptions() {\n        return !this.props.dropdown || (this.isOpened && this.hasOptions);\n    }\n    loadOptions(options, request) {\n        if (typeof options === \"function\") {\n            return options(request);\n        } else {\n            return options;\n        }\n    }\n    makeOption(option) {\n        return {\n            cssClass: \"\",\n            data: {},\n            ...option,\n            id: ++this.nextOptionId,\n            unselectable: !option.onSelect,\n        };\n    }\n    makeSource(source) {\n        return {\n            id: ++this.nextSourceId,\n            options: [],\n            isLoading: false,\n            placeholder: source.placeholder,\n            optionSlot: source.optionSlot,\n        };\n    }\n\n    isActiveSourceOption([sourceIndex, optionIndex]) {\n        return (\n            this.state.activeSourceOption &&\n            this.state.activeSourceOption[0] === sourceIndex &&\n            this.state.activeSourceOption[1] === optionIndex\n        );\n    }\n\n    selectOption(option) {\n        this.inEdition = false;\n        if (option.unselectable) {\n            return;\n        }\n\n        if (this.props.resetOnSelect) {\n            this.inputRef.el.value = \"\";\n        }\n        this.isOptionSelected = true;\n        this.forceValFromProp = true;\n        option.onSelect();\n        this.close();\n    }\n\n    navigate(direction) {\n        let step = Math.sign(direction);\n        if (!step) {\n            this.state.activeSourceOption = null;\n            step = 1;\n        } else {\n            this.state.navigationRev++;\n        }\n\n        do {\n            if (this.state.activeSourceOption) {\n                let [sourceIndex, optionIndex] = this.state.activeSourceOption;\n                let source = this.sources[sourceIndex];\n\n                optionIndex += step;\n                if (0 > optionIndex || optionIndex >= source.options.length) {\n                    sourceIndex += step;\n                    source = this.sources[sourceIndex];\n\n                    while (source && source.isLoading) {\n                        sourceIndex += step;\n                        source = this.sources[sourceIndex];\n                    }\n\n                    if (source) {\n                        optionIndex = step < 0 ? source.options.length - 1 : 0;\n                    }\n                }\n\n                this.state.activeSourceOption = source ? [sourceIndex, optionIndex] : null;\n            } else {\n                let sourceIndex = step < 0 ? this.sources.length - 1 : 0;\n                let source = this.sources[sourceIndex];\n\n                while (source && source.isLoading) {\n                    sourceIndex += step;\n                    source = this.sources[sourceIndex];\n                }\n\n                if (source) {\n                    const optionIndex = step < 0 ? source.options.length - 1 : 0;\n                    if (optionIndex < source.options.length) {\n                        this.state.activeSourceOption = [sourceIndex, optionIndex];\n                    }\n                }\n            }\n        } while (this.activeOption?.unselectable);\n    }\n\n    onInputBlur() {\n        if (this.ignoreBlur) {\n            this.ignoreBlur = false;\n            return;\n        }\n        // If selectOnBlur is true, we select the first element\n        // of the autocomplete suggestions list, if this element exists\n        if (this.props.selectOnBlur && !this.isOptionSelected && this.sources[0]) {\n            const firstOption = this.sources[0].options[0];\n            if (firstOption) {\n                this.state.activeSourceOption = firstOption.unselectable ? null : [0, 0];\n                this.selectOption(this.activeOption);\n            }\n        }\n        this.props.onBlur({\n            inputValue: this.inputRef.el.value,\n        });\n        this.inEdition = false;\n        this.isOptionSelected = false;\n    }\n    onInputClick() {\n        if (!this.isOpened && this.props.searchOnInputClick) {\n            this.open(this.inputRef.el.value.trim() !== this.props.value.trim());\n        } else {\n            this.close();\n        }\n    }\n    onInputChange(ev) {\n        if (this.ignoreBlur) {\n            ev.stopImmediatePropagation();\n        }\n        this.props.onChange({\n            inputValue: this.inputRef.el.value,\n        });\n    }\n    async onInput() {\n        this.inEdition = true;\n        this.pendingPromise = this.pendingPromise || new Deferred();\n        this.loadingPromise = this.pendingPromise;\n        this.debouncedProcessInput();\n    }\n\n    onInputFocus(ev) {\n        this.inputRef.el.setSelectionRange(0, this.inputRef.el.value.length);\n        this.props.onFocus(ev);\n    }\n\n    get autoCompleteRootClass() {\n        let classList = \"\";\n        if (this.props.class) {\n            classList += this.props.class;\n        }\n        if (this.props.dropdown) {\n            classList += \" dropdown\";\n        }\n        return classList;\n    }\n\n    get ulDropdownClass() {\n        return mergeClasses(this.props.menuCssClass, {\n            \"dropdown-menu ui-autocomplete\": this.props.dropdown,\n            \"list-group\": !this.props.dropdown,\n        });\n    }\n\n    async onInputKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        const isSelectKey = hotkey === \"enter\" || hotkey === \"tab\";\n\n        if (this.loadingPromise && isSelectKey) {\n            if (hotkey === \"enter\") {\n                ev.stopPropagation();\n                ev.preventDefault();\n            }\n\n            await this.loadingPromise;\n        }\n\n        switch (hotkey) {\n            case \"enter\":\n                if (!this.isOpened || !this.state.activeSourceOption) {\n                    return;\n                }\n                this.selectOption(this.activeOption);\n                break;\n            case \"escape\":\n                if (!this.isOpened) {\n                    return;\n                }\n                this.cancel();\n                break;\n            case \"tab\":\n            case \"shift+tab\":\n                if (!this.isOpened) {\n                    return;\n                }\n                if (\n                    this.props.autoSelect &&\n                    this.state.activeSourceOption &&\n                    (this.state.navigationRev > 0 || this.inputRef.el.value.length > 0)\n                ) {\n                    this.selectOption(this.activeOption);\n                }\n                this.close();\n                return;\n            case \"arrowup\":\n                this.navigate(-1);\n                if (!this.isOpened) {\n                    this.open(true);\n                }\n                this.scroll();\n                break;\n            case \"arrowdown\":\n                this.navigate(+1);\n                if (!this.isOpened) {\n                    this.open(true);\n                }\n                this.scroll();\n                break;\n            default:\n                return;\n        }\n\n        ev.stopPropagation();\n        ev.preventDefault();\n    }\n\n    onOptionMouseEnter(indices) {\n        if (!this.mouseSelectionActive) {\n            return;\n        }\n\n        const [sourceIndex, optionIndex] = indices;\n        if (this.sources[sourceIndex].options[optionIndex]?.unselectable) {\n            this.state.activeSourceOption = null;\n        } else {\n            this.state.activeSourceOption = indices;\n        }\n    }\n    onOptionMouseLeave() {\n        this.state.activeSourceOption = null;\n    }\n    onOptionClick(option) {\n        this.selectOption(option);\n        this.inputRef.el.focus();\n    }\n    onOptionPointerDown(option, ev) {\n        this.ignoreBlur = true;\n        if (option.unselectable) {\n            ev.preventDefault();\n        }\n    }\n\n    externalClose(ev) {\n        if (this.isOpened && !this.root.el.contains(ev.target)) {\n            this.cancel();\n        }\n    }\n\n    scroll() {\n        if (!this.activeSourceOptionId) {\n            return;\n        }\n        if (isScrollableY(this.listRef.el)) {\n            scrollTo(this.listRef.el.querySelector(`#${this.activeSourceOptionId}`));\n        }\n    }\n}\n", "/**\n * Builder for BarcodeDetector-like polyfill class using ZXing library.\n *\n * @param {ZXing} ZXing Zxing library\n * @returns {class} ZxingBarcodeDetector class\n */\nexport function buildZXingBarcodeDetector(ZXing) {\n    const ZXingFormats = new Map([\n        [\"aztec\", ZXing.BarcodeFormat.AZTEC],\n        [\"code_39\", ZXing.BarcodeFormat.CODE_39],\n        [\"code_128\", ZXing.BarcodeFormat.CODE_128],\n        [\"data_matrix\", ZXing.BarcodeFormat.DATA_MATRIX],\n        [\"ean_8\", ZXing.BarcodeFormat.EAN_8],\n        [\"ean_13\", ZXing.BarcodeFormat.EAN_13],\n        [\"itf\", ZXing.BarcodeFormat.ITF],\n        [\"pdf417\", ZXing.BarcodeFormat.PDF_417],\n        [\"qr_code\", ZXing.BarcodeFormat.QR_CODE],\n        [\"upc_a\", ZXing.BarcodeFormat.UPC_A],\n        [\"upc_e\", ZXing.BarcodeFormat.UPC_E],\n    ]);\n\n    const allSupportedFormats = Array.from(ZXingFormats.keys());\n\n    /**\n     * ZXingBarcodeDetector class\n     *\n     * BarcodeDetector-like polyfill class using ZXing library.\n     * API follows the Shape Detection Web API (specifically Barcode Detection).\n     */\n    class ZXingBarcodeDetector {\n        /**\n         * @param {object} opts\n         * @param {Array} opts.formats list of codes' formats to detect\n         */\n        constructor(opts = {}) {\n            const formats = opts.formats || allSupportedFormats;\n            const hints = new Map([\n                [\n                    ZXing.DecodeHintType.POSSIBLE_FORMATS,\n                    formats.map((format) => ZXingFormats.get(format)),\n                ],\n                // Enable Scanning at 90 degrees rotation\n                // https://github.com/zxing-js/library/issues/291\n                [ZXing.DecodeHintType.TRY_HARDER, true],\n            ]);\n            this.reader = new ZXing.MultiFormatReader();\n            this.reader.setHints(hints);\n        }\n\n        /**\n         * Detect codes in image.\n         *\n         * @param {HTMLVideoElement} video source video element\n         * @returns {Promise<Array>} array of detected codes\n         */\n        async detect(video) {\n            if (!(video instanceof HTMLVideoElement)) {\n                throw new DOMException(\n                    \"imageDataFrom() requires an HTMLVideoElement\",\n                    \"InvalidArgumentError\"\n                );\n            }\n            if (!isVideoElementReady(video)) {\n                throw new DOMException(\"HTMLVideoElement is not ready\", \"InvalidStateError\");\n            }\n            const canvas = document.createElement(\"canvas\");\n\n            let barcodeArea;\n            if (this.cropArea && (this.cropArea.x || this.cropArea.y)) {\n                barcodeArea = this.cropArea;\n            } else {\n                barcodeArea = {\n                    x: 0,\n                    y: 0,\n                    width: video.videoWidth,\n                    height: video.videoHeight,\n                };\n            }\n            canvas.width = barcodeArea.width;\n            canvas.height = barcodeArea.height;\n\n            const ctx = canvas.getContext(\"2d\");\n\n            ctx.drawImage(\n                video,\n                barcodeArea.x,\n                barcodeArea.y,\n                barcodeArea.width,\n                barcodeArea.height,\n                0,\n                0,\n                barcodeArea.width,\n                barcodeArea.height\n            );\n\n            const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(canvas);\n            const binaryBitmap = new ZXing.BinaryBitmap(new ZXing.HybridBinarizer(luminanceSource));\n            try {\n                const result = this.reader.decodeWithState(binaryBitmap);\n                const { resultPoints } = result;\n                const boundingBox = DOMRectReadOnly.fromRect({\n                    x: resultPoints[0].x,\n                    y: resultPoints[0].y,\n                    height: Math.max(1, Math.abs(resultPoints[1].y - resultPoints[0].y)),\n                    width: Math.max(1, Math.abs(resultPoints[1].x - resultPoints[0].x)),\n                });\n                const cornerPoints = resultPoints;\n                const format = Array.from(ZXingFormats).find(\n                    ([k, val]) => val === result.getBarcodeFormat()\n                );\n                const rawValue = result.getText();\n                return [\n                    {\n                        boundingBox,\n                        cornerPoints,\n                        format,\n                        rawValue,\n                    },\n                ];\n            } catch (err) {\n                if (err.name === \"NotFoundException\") {\n                    return [];\n                }\n                throw err;\n            }\n        }\n\n        setCropArea(cropArea) {\n            this.cropArea = cropArea;\n        }\n    }\n\n    /**\n     * Supported codes formats\n     *\n     * @static\n     * @returns {Promise<string[]>}\n     */\n    ZXingBarcodeDetector.getSupportedFormats = async () => allSupportedFormats;\n\n    return ZXingBarcodeDetector;\n}\n\n/**\n * Check for HTMLVideoElement readiness.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState\n */\nconst HAVE_NOTHING = 0;\nconst HAVE_METADATA = 1;\nexport function isVideoElementReady(video) {\n    return ![HAVE_NOTHING, HAVE_METADATA].includes(video.readyState);\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { Component, useState } from \"@odoo/owl\";\nimport { BarcodeVideoScanner, isBarcodeScannerSupported } from \"./barcode_video_scanner\";\n\nexport class BarcodeDialog extends Component {\n    static template = \"web.BarcodeDialog\";\n    static components = {\n        BarcodeVideoScanner,\n        Dialog,\n    };\n    static props = [\"facingMode\", \"close\", \"onResult\", \"onError\"];\n\n    setup() {\n        this.state = useState({\n            barcodeScannerSupported: isBarcodeScannerSupported(),\n            errorMessage: _t(\"Check your browser permissions\"),\n        });\n    }\n\n    /**\n     * Detection success handler\n     *\n     * @param {string} result found code\n     */\n    onResult(result) {\n        this.props.close();\n        this.props.onResult(result);\n    }\n\n    /**\n     * Detection error handler\n     *\n     * @param {Error} error\n     */\n    onError(error) {\n        this.state.barcodeScannerSupported = false;\n        this.state.errorMessage = error.message;\n    }\n}\n\n/**\n * Opens the BarcodeScanning dialog and begins code detection using the device's camera.\n *\n * @returns {Promise<string>} resolves when a {qr,bar}code has been detected\n */\nexport async function scanBarcode(env, facingMode = \"environment\") {\n    let res;\n    let rej;\n    const promise = new Promise((resolve, reject) => {\n        res = resolve;\n        rej = reject;\n    });\n    env.services.dialog.add(BarcodeDialog, {\n        facingMode,\n        onResult: (result) => res(result),\n        onError: (error) => rej(error),\n    });\n    return promise;\n}\n", "/* global BarcodeDetector */\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { delay } from \"@web/core/utils/concurrency\";\nimport { loadJS } from \"@web/core/assets\";\nimport { isVideoElementReady, buildZXingBarcodeDetector } from \"./ZXingBarcodeDetector\";\nimport { CropOverlay } from \"./crop_overlay\";\nimport { Component, onMounted, onWillStart, onWillUnmount, status, useRef, useState } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { pick } from \"@web/core/utils/objects\";\n\nexport class BarcodeVideoScanner extends Component {\n    static template = \"web.BarcodeVideoScanner\";\n    static components = {\n        CropOverlay,\n    };\n    static props = {\n        cssClass: { type: String, optional: true },\n        facingMode: {\n            type: String,\n            validate: (fm) => [\"environment\", \"left\", \"right\", \"user\"].includes(fm),\n        },\n        close: { type: Function, optional: true },\n        onReady: { type: Function, optional: true },\n        onResult: Function,\n        onError: Function,\n        placeholder: { type: String, optional: true },\n        delayBetweenScan: { type: Number, optional: true },\n    };\n    static defaultProps = {\n        cssClass: \"w-100 h-100\",\n    };\n    /**\n     * @override\n     */\n    setup() {\n        this.videoPreviewRef = useRef(\"videoPreview\");\n        this.detectorTimeout = null;\n        this.stream = null;\n        this.detector = null;\n        this.overlayInfo = {};\n        this.zoomRatio = 1;\n        this.scanPaused = false;\n        this.state = useState({\n            isReady: false,\n        });\n\n        onWillStart(async () => {\n            let DetectorClass;\n            // Use Barcode Detection API if available.\n            // As support is still bleeding edge (mainly Chrome on Android),\n            // also provides a fallback using ZXing library.\n            if (\"BarcodeDetector\" in window) {\n                DetectorClass = BarcodeDetector;\n            } else {\n                await loadJS(\"/web/static/lib/zxing-library/zxing-library.js\");\n                DetectorClass = buildZXingBarcodeDetector(window.ZXing);\n            }\n            const formats = await DetectorClass.getSupportedFormats();\n            this.detector = new DetectorClass({ formats });\n        });\n\n        onMounted(async () => {\n            const constraints = {\n                video: { facingMode: this.props.facingMode },\n                audio: false,\n            };\n\n            try {\n                this.stream = await browser.navigator.mediaDevices.getUserMedia(constraints);\n            } catch (err) {\n                const errors = {\n                    NotFoundError: _t(\"No device can be found.\"),\n                    NotAllowedError: _t(\"Odoo needs your authorization first.\"),\n                };\n                const errorMessage = _t(\"Could not start scanning. %(message)s\", {\n                    message: errors[err.name] || err.message,\n                });\n                this.props.onError(new Error(errorMessage));\n                return;\n            }\n            if (!this.videoPreviewRef.el) {\n                this.cleanStreamAndTimeout();\n                const errorMessage = _t(\"Barcode Video Scanner could not be mounted properly.\");\n                this.props.onError(new Error(errorMessage));\n                return;\n            }\n            this.videoPreviewRef.el.srcObject = this.stream;\n            const ready = await this.isVideoReady();\n            if (!ready) {\n                return;\n            }\n            const { height, width } = getComputedStyle(this.videoPreviewRef.el);\n            const divWidth = width.slice(0, -2);\n            const divHeight = height.slice(0, -2);\n            const tracks = this.stream.getVideoTracks();\n            if (tracks.length) {\n                const [track] = tracks;\n                const settings = track.getSettings();\n                this.zoomRatio = Math.min(divWidth / settings.width, divHeight / settings.height);\n            }\n            this.detectorTimeout = setTimeout(this.detectCode.bind(this), 100);\n        });\n\n        onWillUnmount(() => this.cleanStreamAndTimeout());\n    }\n\n    cleanStreamAndTimeout() {\n        clearTimeout(this.detectorTimeout);\n        this.detectorTimeout = null;\n        if (this.stream) {\n            this.stream.getTracks().forEach((track) => track.stop());\n            this.stream = null;\n        }\n    }\n\n    isZXingBarcodeDetector() {\n        return this.detector && this.detector.__proto__.constructor.name === \"ZXingBarcodeDetector\";\n    }\n\n    /**\n     * Check for camera preview element readiness\n     *\n     * @returns {Promise} resolves when the video element is ready\n     */\n    async isVideoReady() {\n        // FIXME: even if it shouldn't happened, a timeout could be useful here.\n        while (!isVideoElementReady(this.videoPreviewRef.el)) {\n            await delay(10);\n            if (status(this) === \"destroyed\"){\n                return false;\n            }\n        }\n        this.state.isReady = true;\n        if (this.props.onReady) {\n            this.props.onReady();\n        }\n        return true;\n    }\n\n    onResize(overlayInfo) {\n        this.overlayInfo = overlayInfo;\n        if (this.isZXingBarcodeDetector()) {\n            // TODO need refactoring when ZXing will support multiple result in one scan\n            // https://github.com/zxing-js/library/issues/346\n            this.detector.setCropArea(this.adaptValuesWithRatio(this.overlayInfo, true));\n        }\n    }\n\n    /**\n     * Attempt to detect codes in the current camera preview's frame\n     */\n    async detectCode() {\n        let barcodeDetected = false;\n        let codes = [];\n        try {\n            codes = await this.detector.detect(this.videoPreviewRef.el);\n        } catch (err) {\n            this.props.onError(err);\n        }\n        for (const code of codes) {\n            if (\n                !this.isZXingBarcodeDetector() &&\n                this.overlayInfo.x !== undefined &&\n                this.overlayInfo.y !== undefined\n            ) {\n                const { x, y, width, height } = this.adaptValuesWithRatio(code.boundingBox);\n                if (\n                    x < this.overlayInfo.x ||\n                    x + width > this.overlayInfo.x + this.overlayInfo.width ||\n                    y < this.overlayInfo.y ||\n                    y + height > this.overlayInfo.y + this.overlayInfo.height\n                ) {\n                    continue;\n                }\n            }\n            barcodeDetected = true;\n            this.barcodeDetected(code.rawValue);\n            break;\n        }\n        if (this.stream && (!barcodeDetected || !this.props.delayBetweenScan)) {\n            this.detectorTimeout = setTimeout(this.detectCode.bind(this), 100);\n        }\n    }\n\n    barcodeDetected(barcode) {\n        if (this.props.delayBetweenScan && !this.scanPaused) {\n            this.scanPaused = true;\n            this.detectorTimeout = setTimeout(() => {\n                this.scanPaused = false;\n                this.detectorTimeout = setTimeout(this.detectCode.bind(this), 100);\n            }, this.props.delayBetweenScan);\n        }\n        this.props.onResult(barcode);\n    }\n\n    adaptValuesWithRatio(domRect, dividerRatio = false) {\n        const newObject = pick(domRect, \"x\", \"y\", \"width\", \"height\");\n        for (const key of Object.keys(newObject)) {\n            if (dividerRatio) {\n                newObject[key] /= this.zoomRatio;\n            } else {\n                newObject[key] *= this.zoomRatio;\n            }\n        }\n        return newObject;\n    }\n}\n\n/**\n * Check for BarcodeScanner support\n * @returns {boolean}\n */\nexport function isBarcodeScannerSupported() {\n    return Boolean(browser.navigator.mediaDevices && browser.navigator.mediaDevices.getUserMedia);\n}\n", "import { Component, useRef, onPatched } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { clamp } from \"@web/core/utils/numbers\";\n\nexport class CropOverlay extends Component {\n    static template = \"web.CropOverlay\";\n    static props = {\n        onResize: Function,\n        isReady: Boolean,\n        slots: {\n            type: Object,\n            shape: {\n                default: {},\n            },\n        },\n    };\n\n    setup() {\n        this.localStorageKey = \"o-barcode-scanner-overlay\";\n        this.cropContainerRef = useRef(\"crop-container\");\n        this.isMoving = false;\n        this.boundaryOverlay = {};\n        this.relativePosition = {\n            x: 0,\n            y: 0,\n        };\n        onPatched(() => {\n            this.setupCropRect();\n        });\n    }\n\n    setupCropRect() {\n        if (!this.props.isReady) {\n            return;\n        }\n        this.computeDefaultPoint();\n        this.computeOverlayPosition();\n        this.calculateAndSetTransparentRect();\n        this.executeOnResizeCallback();\n    }\n\n    boundPoint(pointValue, boundaryRect) {\n        return {\n            x: clamp(pointValue.x, boundaryRect.left, boundaryRect.left + boundaryRect.width),\n            y: clamp(pointValue.y, boundaryRect.top, boundaryRect.top + boundaryRect.height),\n        };\n    }\n\n    calculateAndSetTransparentRect() {\n        const cropTransparentRect = this.getTransparentRec(\n            this.relativePosition,\n            this.boundaryOverlay\n        );\n        this.setCropValue(cropTransparentRect, this.relativePosition);\n    }\n\n    computeOverlayPosition() {\n        const cropOverlayElement = this.cropContainerRef.el.querySelector(\".o_crop_overlay\");\n        this.boundaryOverlay = cropOverlayElement.getBoundingClientRect();\n    }\n\n    executeOnResizeCallback() {\n        const transparentRec = this.getTransparentRec(this.relativePosition, this.boundaryOverlay);\n        browser.localStorage.setItem(this.localStorageKey, JSON.stringify(transparentRec));\n        this.props.onResize({\n            ...transparentRec,\n            width: this.boundaryOverlay.width - 2 * transparentRec.x,\n            height: this.boundaryOverlay.height - 2 * transparentRec.y,\n        });\n    }\n\n    computeDefaultPoint() {\n        const firstChildComputedStyle = getComputedStyle(this.cropContainerRef.el.firstChild);\n        const elementWidth = firstChildComputedStyle.width.slice(0, -2);\n        const elementHeight = firstChildComputedStyle.height.slice(0, -2);\n\n        const stringSavedPoint = browser.localStorage.getItem(this.localStorageKey);\n        if (stringSavedPoint) {\n            const savedPoint = JSON.parse(stringSavedPoint);\n            this.relativePosition = {\n                x: clamp(savedPoint.x, 0, elementWidth),\n                y: clamp(savedPoint.y, 0, elementHeight),\n            };\n        } else {\n            const stepWidth = elementWidth / 10;\n            const width = stepWidth * 8;\n            const height = width / 4;\n            const startY = elementHeight / 2 - height / 2;\n            this.relativePosition = {\n                x: stepWidth + width,\n                y: startY + height,\n            };\n        }\n    }\n    getTransparentRec(point, rect) {\n        const middleX = rect.width / 2;\n        const middleY = rect.height / 2;\n        const newDeltaX = Math.abs(point.x - middleX);\n        const newDeltaY = Math.abs(point.y - middleY);\n        return {\n            x: middleX - newDeltaX,\n            y: middleY - newDeltaY,\n        };\n    }\n\n    setCropValue(point, iconPoint) {\n        if (!iconPoint) {\n            iconPoint = point;\n        }\n        this.cropContainerRef.el.style.setProperty(\"--o-crop-x\", `${point.x}px`);\n        this.cropContainerRef.el.style.setProperty(\"--o-crop-y\", `${point.y}px`);\n        this.cropContainerRef.el.style.setProperty(\"--o-crop-icon-x\", `${iconPoint.x}px`);\n        this.cropContainerRef.el.style.setProperty(\"--o-crop-icon-y\", `${iconPoint.y}px`);\n    }\n\n    pointerDown(event) {\n        event.preventDefault();\n        if (event.target.matches(\".o_crop_icon\")) {\n            this.computeOverlayPosition();\n            this.isMoving = true;\n        }\n    }\n\n    pointerMove(event) {\n        if (!this.isMoving) {\n            return;\n        }\n        let eventPosition;\n        if (event.touches && event.touches.length) {\n            eventPosition = event.touches[0];\n        } else {\n            eventPosition = event;\n        }\n        const { clientX, clientY } = eventPosition;\n        const restrictedPosition = this.boundPoint(\n            {\n                x: clientX,\n                y: clientY,\n            },\n            this.boundaryOverlay\n        );\n        this.relativePosition = {\n            x: restrictedPosition.x - this.boundaryOverlay.left,\n            y: restrictedPosition.y - this.boundaryOverlay.top,\n        };\n        this.calculateAndSetTransparentRect(this.relativePosition);\n    }\n\n    pointerUp(event) {\n        this.isMoving = false;\n        this.executeOnResizeCallback();\n    }\n}\n", "/**\n * BottomSheet\n *\n * @class\n */\nimport { Component, useState, useRef, onMounted, useExternalListener } from \"@odoo/owl\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { useForwardRefToParent } from \"@web/core/utils/hooks\";\nimport { useThrottleForAnimation } from \"@web/core/utils/timing\";\nimport { compensateScrollbar } from \"@web/core/utils/scrolling\";\nimport { getViewportDimensions, useViewportChange } from \"@web/core/utils/dvu\";\nimport { clamp } from \"@web/core/utils/numbers\";\nimport { browser } from \"@web/core/browser/browser\";\n\nexport class BottomSheet extends Component {\n    static template = \"web.BottomSheet\";\n\n    static defaultProps = {\n        class: \"\",\n    };\n\n    static props = {\n        // Main props\n        component: { type: Function },\n        componentProps: { optional: true, type: Object },\n        close: { type: Function },\n\n        class: { optional: true },\n        role: { optional: true, type: String },\n\n        // Technical props\n        ref: { optional: true, type: Function },\n        slots: { optional: true, type: Object },\n    };\n\n    setup() {\n        this.maxHeightPercent = 90;\n\n        this.state = useState({\n            isPositionedReady: false, // Sheet is ready for display\n            isSnappingEnabled: false,\n            isDismissing: false, // Sheet is being dismissed\n            progress: 0, // Visual progress (0-1)\n        });\n\n        // Measurements and configuration\n        this.measurements = {\n            viewportHeight: 0,\n            naturalHeight: 0,\n            maxHeight: 0,\n            dismissThreshold: 0,\n        };\n\n        // Popover Ref Requirement\n        useForwardRefToParent(\"ref\");\n\n        // References\n        this.containerRef = useRef(\"container\");\n        this.scrollRailRef = useRef(\"scrollRail\");\n        this.sheetRef = useRef(\"sheet\");\n        this.sheetBodyRef = useRef(\"ref\");\n\n        // Create throttled version for onScroll\n        this.throttledOnScroll = useThrottleForAnimation(this.onScroll.bind(this));\n\n        // Adapt dimensions when mobile virtual-keyboards or browsers bars toggle\n        useViewportChange(() => {\n            if (this.state.isPositionedReady && !this.state.isDismissing) {\n                this.updateDimensions();\n            }\n        });\n\n        // Handle \"ESC\" key press.\n        useHotkey(\"escape\", () => this.slideOut());\n\n        // Handle mobile \"back\" gesture and \"back\" navigation button.\n        // Push a history state when the BottomSheet opens, intercept the browser's\n        // history events, prevents navigation by pushing another state and closes the sheet.\n        window.history.pushState({ bottomSheet: true }, \"\");\n        this.handlePopState = () => {\n            if (this.state.isPositionedReady && !this.state.isDismissing) {\n                window.history.pushState({ bottomSheet: true }, \"\");\n                this.slideOut();\n            }\n        };\n        useExternalListener(window, \"popstate\", this.handlePopState);\n\n        onMounted(() => {\n            const isReduced =\n                browser.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||\n                browser.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;\n\n            this.prefersReducedMotion =\n                isReduced || getComputedStyle(this.containerRef.el).animationName === \"none\";\n\n            this.initializeSheet();\n            compensateScrollbar(this.scrollRailRef.el, true, true, \"padding-right\");\n        });\n    }\n\n    /**\n     * Main initialization method for the sheet\n     * Sets up measurements, snap points, and event handlers\n     */\n    initializeSheet() {\n        if (!this.containerRef.el || !this.scrollRailRef.el || !this.sheetRef.el) {\n            return;\n        }\n\n        // Step 1: Take measurements\n        this.measureDimensions();\n\n        // Step 2: Apply Dimensions\n        this.applyDimensions();\n\n        // Step 3: Set initial position\n        this.positionSheet();\n\n        // Step 4: Setup event handlers after everything has been properly resized and positioned\n        this.setupEventHandlers();\n\n        // Step 5: Mark as ready\n        this.state.isPositionedReady = true;\n\n        if (this.prefersReducedMotion) {\n            this.state.isSnappingEnabled = true;\n        } else {\n            this.sheetRef.el?.addEventListener(\n                \"animationend\",\n                () => (this.state.isSnappingEnabled = true),\n                {\n                    once: true,\n                }\n            );\n            this.sheetRef.el?.addEventListener(\n                \"animationcancel\",\n                () => (this.state.isSnappingEnabled = true),\n                {\n                    once: true,\n                }\n            );\n        }\n    }\n\n    /**\n     * Updates dimensions when viewport changes\n     * Recalculates measurements and snap points while preserving extended state\n     */\n    updateDimensions() {\n        // Temporarily disable snapping during update\n        this.state.isSnappingEnabled = false;\n\n        // Update measurements with new viewport dimensions\n        this.measureDimensions();\n        this.applyDimensions();\n\n        // // Update scroll position\n        const scrollTop = this.scrollRailRef.el.scrollTop;\n\n        // Update progress value\n        this.updateProgressValue(scrollTop);\n    }\n\n    /**\n     * Takes measurements of viewport and sheet dimensions\n     * Calculates natural height and other key measurements\n     */\n    measureDimensions() {\n        const viewportHeight = getViewportDimensions().height;\n\n        // Calculate heights based on percentages\n        const maxHeightPx = (this.maxHeightPercent / 100) * viewportHeight;\n\n        // Reset any previously set constraints to measure natural height\n        const sheet = this.sheetRef.el;\n        sheet.style.removeProperty(\"min-height\");\n        sheet.style.removeProperty(\"height\");\n\n        const naturalHeight = sheet.offsetHeight;\n        const initialHeightPx = Math.min(naturalHeight, maxHeightPx);\n\n        // Store all measurements\n        this.measurements = {\n            viewportHeight,\n            naturalHeight,\n            initialHeight: initialHeightPx,\n            maxHeight: maxHeightPx,\n            dismissThreshold: Math.min(initialHeightPx * 0.3, 100),\n        };\n    }\n\n    /**\n     * Applies calculated dimensions to the DOM elements\n     * Sets CSS variables and styles based on measurements and snap points\n     */\n    applyDimensions() {\n        const rail = this.scrollRailRef.el;\n\n        // Convert heights to dvh percentages for CSS variables\n        const heightPercent = Math.min(\n            (this.measurements.initialHeight / this.measurements.viewportHeight) * 100,\n            this.maxHeightPercent\n        );\n\n        // Set CSS variables for heights\n        rail.style.setProperty(\"--sheet-height\", `${heightPercent}dvh`);\n        rail.style.setProperty(\"--sheet-max-height\", `${this.measurements.viewportHeight}px`);\n        rail.style.setProperty(\"--dismiss-height\", `${this.measurements.initialHeight || 0}px`);\n    }\n\n    /**\n     * Sets the initial position of the sheet\n     * Configures initial scroll position and overflow behavior\n     */\n    positionSheet() {\n        const scrollRail = this.scrollRailRef.el;\n        const bodyContent = this.sheetBodyRef.el;\n\n        const scrollValue = this.measurements.maxHeight;\n\n        // Configure body content overflow\n        if (bodyContent) {\n            bodyContent.style.overflowY = \"auto\";\n        }\n\n        // Set scroll position\n        scrollRail.scrollTop = scrollValue || 0;\n        scrollRail.style.containerType = \"scroll-state size\";\n    }\n\n    /**\n     * Sets up event handlers for scroll and touch events\n     */\n    setupEventHandlers() {\n        const scrollRail = this.scrollRailRef.el;\n\n        // Add scroll event listener\n        scrollRail.addEventListener(\"scroll\", this.throttledOnScroll);\n    }\n\n    /**\n     * Handles scroll events on the rail element\n     * Updates progress, handles position snapping, and triggers dismissal\n     */\n    onScroll() {\n        if (!this.scrollRailRef.el) {\n            return;\n        }\n\n        const scrollTop = this.scrollRailRef.el.scrollTop;\n\n        // Update progress value for visual effects\n        this.updateProgressValue(scrollTop);\n\n        // Check for dismissal condition\n        if (scrollTop < this.measurements.dismissThreshold) {\n            this.slideOut();\n        }\n    }\n\n    /**\n     * Calculates and updates the progress value based on scroll position\n     *\n     * @param {number} scrollTop - Current scroll position\n     */\n    updateProgressValue(scrollTop) {\n        const initialPosition = this.measurements.naturalHeight;\n        const progress = clamp(scrollTop / initialPosition, 0, 1);\n\n        if (Math.abs(this.state.progress - progress) > 0.01) {\n            this.state.progress = progress;\n        }\n    }\n\n    /**\n     * Initiates the slide out animation and dismissal\n     */\n    slideOut() {\n        // Prevent duplicate calls\n        if (this.state.isDismissing) {\n            return;\n        }\n\n        if (this.prefersReducedMotion) {\n            this.props.close?.();\n        } else {\n            this.sheetRef.el?.addEventListener(\"animationend\", () => this.props.close?.(), {\n                once: true,\n            });\n            this.sheetRef.el?.addEventListener(\"animationcancel\", () => this.props.close?.(), {\n                once: true,\n            });\n        }\n\n        // Update state to trigger animation\n        this.state.isDismissing = true;\n        this.state.isSnappingEnabled = false;\n    }\n\n    /**\n     * Closes the sheet (public API)\n     */\n    close() {\n        this.slideOut();\n    }\n\n    /**\n     * Handles back button press (public API)\n     */\n    back() {\n        if (this.props.onBack) {\n            this.props.onBack();\n        } else {\n            this.slideOut();\n        }\n    }\n}\n", "import { markRaw } from \"@odoo/owl\";\nimport { BottomSheet } from \"@web/core/bottom_sheet/bottom_sheet\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * @typedef {{\n *   env?: object;\n *   onClose?: () => void;\n *   class?: string;\n *   role?: string;\n *   ref?: Function;\n *   useBottomSheet?: Boolean;\n * }} PopoverServiceAddOptions\n *\n * @typedef {ReturnType<popoverService[\"start\"]>[\"add\"]} PopoverServiceAddFunction\n */\n\nexport const popoverService = {\n    dependencies: [\"overlay\"],\n    start(_, { overlay }) {\n        let bottomSheetCount = 0;\n        /**\n         * Signals the manager to add a popover.\n         *\n         * @param {HTMLElement} target\n         * @param {typeof import(\"@odoo/owl\").Component} component\n         * @param {object} [props]\n         * @param {PopoverServiceAddOptions} [options]\n         * @returns {() => void}\n         */\n        const add = (target, component, props = {}, options = {}) => {\n            function removeAndUpdateCount() {\n                _remove();\n                bottomSheetCount--;\n                if (bottomSheetCount === 0) {\n                    document.body.classList.remove(\"bottom-sheet-open\");\n                } else if (bottomSheetCount === 1) {\n                    document.body.classList.remove(\"bottom-sheet-open-multiple\");\n                }\n            }\n            const _remove = overlay.add(\n                BottomSheet,\n                {\n                    close: removeAndUpdateCount,\n                    component,\n                    componentProps: markRaw(props),\n                    ref: options.ref,\n                    class: options.class,\n                    role: options.role,\n                },\n                {\n                    env: options.env,\n                    onRemove: options.onClose,\n                    rootId: target.getRootNode()?.host?.id,\n                }\n            );\n            bottomSheetCount++;\n            if (bottomSheetCount === 1) {\n                document.body.classList.add(\"bottom-sheet-open\");\n            } else if (bottomSheetCount > 1) {\n                document.body.classList.add(\"bottom-sheet-open-multiple\");\n            }\n\n            return removeAndUpdateCount;\n        };\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"bottom_sheet\", popoverService);\n", "/**\n * Browser\n *\n * This file exports an object containing common browser API. It may not look\n * incredibly useful, but it is very convenient when one needs to test code using\n * these methods. With this indirection, it is possible to patch the browser\n * object for a test.\n */\n\nlet sessionStorage;\nlet localStorage;\ntry {\n    sessionStorage = window.sessionStorage;\n    localStorage = window.localStorage;\n    // Safari crashes in Private Browsing\n    localStorage.setItem(\"__localStorage__\", \"true\");\n    localStorage.removeItem(\"__localStorage__\");\n} catch {\n    localStorage = makeRAMLocalStorage();\n    sessionStorage = makeRAMLocalStorage();\n}\n\nexport const browser = {\n    addEventListener: window.addEventListener.bind(window),\n    dispatchEvent: window.dispatchEvent.bind(window),\n    AnalyserNode: window.AnalyserNode,\n    Audio: window.Audio,\n    AudioBufferSourceNode: window.AudioBufferSourceNode,\n    AudioContext: window.AudioContext,\n    AudioWorkletNode: window.AudioWorkletNode,\n    BeforeInstallPromptEvent: window.BeforeInstallPromptEvent?.bind(window),\n    GainNode: window.GainNode,\n    MediaStreamAudioSourceNode: window.MediaStreamAudioSourceNode,\n    removeEventListener: window.removeEventListener.bind(window),\n    setTimeout: window.setTimeout.bind(window),\n    clearTimeout: window.clearTimeout.bind(window),\n    setInterval: window.setInterval.bind(window),\n    clearInterval: window.clearInterval.bind(window),\n    performance: window.performance,\n    requestAnimationFrame: window.requestAnimationFrame.bind(window),\n    cancelAnimationFrame: window.cancelAnimationFrame.bind(window),\n    console: window.console,\n    history: window.history,\n    matchMedia: window.matchMedia.bind(window),\n    navigator,\n    Notification: window.Notification,\n    open: window.open.bind(window),\n    SharedWorker: window.SharedWorker,\n    Worker: window.Worker,\n    XMLHttpRequest: window.XMLHttpRequest,\n    localStorage,\n    sessionStorage,\n    fetch: window.fetch.bind(window),\n    innerHeight: window.innerHeight,\n    innerWidth: window.innerWidth,\n    ontouchstart: window.ontouchstart,\n    BroadcastChannel: window.BroadcastChannel,\n    visualViewport: window.visualViewport,\n};\n\nObject.defineProperty(browser, \"location\", {\n    set(val) {\n        window.location = val;\n    },\n    get() {\n        return window.location;\n    },\n    configurable: true,\n});\n\nObject.defineProperty(browser, \"innerHeight\", {\n    get: () => window.innerHeight,\n    configurable: true,\n});\nObject.defineProperty(browser, \"innerWidth\", {\n    get: () => window.innerWidth,\n    configurable: true,\n});\n\n// -----------------------------------------------------------------------------\n// memory localStorage\n// -----------------------------------------------------------------------------\n\n/**\n * @returns {typeof window[\"localStorage\"]}\n */\nexport function makeRAMLocalStorage() {\n    let store = {};\n    return {\n        setItem(key, value) {\n            const newValue = String(value);\n            store[key] = newValue;\n            window.dispatchEvent(new StorageEvent(\"storage\", { key, newValue }));\n        },\n        getItem(key) {\n            return store[key] ?? null;\n        },\n        clear() {\n            store = {};\n        },\n        removeItem(key) {\n            delete store[key];\n            window.dispatchEvent(new StorageEvent(\"storage\", { key, newValue: null }));\n        },\n        get length() {\n            return Object.keys(store).length;\n        },\n        key() {\n            return \"\";\n        },\n    };\n}\n", "import { browser } from \"./browser\";\n\n// -----------------------------------------------------------------------------\n// Feature detection\n// -----------------------------------------------------------------------------\n\n/**\n * True if the browser is based on Chromium (Google Chrome, Opera, Edge).\n */\nexport function isBrowserChrome() {\n    return /Chrome/i.test(browser.navigator.userAgent);\n}\n\nexport function isBrowserFirefox() {\n    return /Firefox/i.test(browser.navigator.userAgent);\n}\n\n/**\n * True if the browser is Microsoft Edge.\n */\nexport function isBrowserMicrosoftEdge() {\n    return /Edg/i.test(browser.navigator.userAgent);\n}\n\n/**\n * true if the browser is based on Safari (Safari, Epiphany)\n *\n * @returns {boolean}\n */\nexport function isBrowserSafari() {\n    return !isBrowserChrome() && browser.navigator.userAgent?.includes(\"Safari\");\n}\n\nexport function isAndroid() {\n    return /Android/i.test(browser.navigator.userAgent);\n}\n\nexport function isIOS() {\n    let isIOSPlatform = false;\n    if (\"platform\" in browser.navigator) {\n        isIOSPlatform = browser.navigator.platform === \"MacIntel\";\n    }\n    return (\n        /(iPad|iPhone|iPod)/i.test(browser.navigator.userAgent) ||\n        (isIOSPlatform && maxTouchPoints() > 1)\n    );\n}\n\nexport function isOtherMobileOS() {\n    return /(webOS|BlackBerry|Windows Phone)/i.test(browser.navigator.userAgent);\n}\n\nexport function isMacOS() {\n    return /Mac/i.test(browser.navigator.userAgent);\n}\n\nexport function isMobileOS() {\n    return isAndroid() || isIOS() || isOtherMobileOS();\n}\n\nexport function isIosApp() {\n    return /OdooMobile \\(iOS\\)/i.test(browser.navigator.userAgent);\n}\n\nexport function isAndroidApp() {\n    return /OdooMobile.+Android/i.test(browser.navigator.userAgent);\n}\n\nexport function isDisplayStandalone() {\n    return browser.matchMedia(\"(display-mode: standalone)\").matches;\n}\n\nexport function hasTouch() {\n    return browser.ontouchstart !== undefined || browser.matchMedia(\"(pointer:coarse)\").matches;\n}\n\nexport function maxTouchPoints() {\n    return browser.navigator.maxTouchPoints || 1;\n}\n\nexport function isVirtualKeyboardSupported() {\n    return \"virtualKeyboard\" in browser.navigator;\n}\n", "import { EventBus } from \"@odoo/owl\";\nimport { omit, pick } from \"../utils/objects\";\nimport { compareUrls, objectToUrlEncodedString } from \"../utils/urls\";\nimport { browser } from \"./browser\";\nimport { isDisplayStandalone } from \"@web/core/browser/feature_detection\";\nimport { slidingWindow } from \"@web/core/utils/arrays\";\nimport { isNumeric } from \"@web/core/utils/strings\";\n\n// Keys that are serialized in the URL as path segments instead of query string\nexport const PATH_KEYS = [\"resId\", \"action\", \"active_id\", \"model\"];\n\nexport const routerBus = new EventBus();\n\nfunction isScopedApp() {\n    return browser.location.href.includes(\"/scoped_app\") && isDisplayStandalone();\n}\n\n/**\n * Casts the given string to a number if possible.\n *\n * @param {string} value\n * @returns {string|number}\n */\nfunction cast(value) {\n    return !value || isNaN(value) ? value : Number(value);\n}\n\n/**\n * @typedef {{ [key: string]: string }} Query\n * @typedef {{ [key: string]: any }} Route\n */\n\nfunction parseString(str) {\n    const parts = str.split(\"&\");\n    const result = {};\n    for (const part of parts) {\n        const [key, value] = part.split(\"=\");\n        const decoded = decodeURIComponent(value || \"\");\n        result[key] = cast(decoded);\n    }\n    return result;\n}\n/**\n * @param {object} values An object with the values of the new state\n * @param {boolean} replace whether the values should replace the state or be\n *  layered on top of the current state\n * @returns {object} the next state of the router\n */\nfunction computeNextState(values, replace) {\n    const nextState = replace ? pick(state, ..._lockedKeys) : { ...state };\n    Object.assign(nextState, values);\n    // Update last entry in the actionStack\n    if (nextState.actionStack?.length) {\n        Object.assign(nextState.actionStack.at(-1), pick(nextState, ...PATH_KEYS));\n    }\n    return sanitizeSearch(nextState);\n}\n\nfunction sanitize(obj, valueToRemove) {\n    return Object.fromEntries(\n        Object.entries(obj)\n            .filter(([, v]) => v !== valueToRemove)\n            .map(([k, v]) => [k, cast(v)])\n    );\n}\n\nfunction sanitizeSearch(search) {\n    return sanitize(search);\n}\n\nfunction sanitizeHash(hash) {\n    return sanitize(hash, \"\");\n}\n\n/**\n * @param {string} hash\n * @returns {any}\n */\nexport function parseHash(hash) {\n    return hash && hash !== \"#\" ? parseString(hash.slice(1)) : {};\n}\n\n/**\n * @param {string} search\n * @returns {any}\n */\nexport function parseSearchQuery(search) {\n    return search ? parseString(search.slice(1)) : {};\n}\n\nfunction pathFromActionState(state) {\n    const path = [];\n    const { action, model, active_id, resId } = state;\n    if (active_id && typeof active_id === \"number\") {\n        path.push(active_id);\n    }\n    if (action) {\n        if (typeof action === \"number\" || action.includes(\".\")) {\n            path.push(`action-${action}`);\n        } else {\n            path.push(action);\n        }\n    } else if (model) {\n        if (model.includes(\".\")) {\n            path.push(model);\n        } else {\n            // A few models don't have a dot at all, we need to distinguish\n            // them from action paths (eg: website)\n            path.push(`m-${model}`);\n        }\n    }\n    if (resId && (typeof resId === \"number\" || resId === \"new\")) {\n        path.push(resId);\n    }\n    return path.join(\"/\");\n}\n\nexport function startUrl() {\n    return isScopedApp() ? \"scoped_app\" : \"odoo\";\n}\n\n/**\n * @param {{ [key: string]: any }} state\n * @returns\n */\nfunction stateToUrl(state) {\n    let path = \"\";\n    const pathKeysToOmit = [..._hiddenKeysFromUrl];\n    const actionStack = (state.actionStack || [state]).map((a) => ({ ...a }));\n    if (actionStack.at(-1)?.action !== \"menu\") {\n        for (const [prevAct, currentAct] of slidingWindow(actionStack, 2).reverse()) {\n            const { action: prevAction, resId: prevResId, active_id: prevActiveId } = prevAct;\n            const { action: currentAction, active_id: currentActiveId } = currentAct;\n            // actions would typically map to a path like `active_id/action/res_id`\n            if (currentActiveId === prevResId) {\n                // avoid doubling up when the active_id is the same as the previous action's res_id\n                delete currentAct.active_id;\n            }\n            if (prevAction === currentAction && !prevResId && currentActiveId === prevActiveId) {\n                //avoid doubling up the action and the active_id when a single-record action is preceded by a multi-record action\n                delete currentAct.action;\n                delete currentAct.active_id;\n            }\n        }\n        const pathSegments = actionStack.map(pathFromActionState).filter(Boolean);\n        if (pathSegments.length) {\n            path = `/${pathSegments.join(\"/\")}`;\n        }\n    }\n    if (state.active_id && typeof state.active_id !== \"number\") {\n        pathKeysToOmit.splice(pathKeysToOmit.indexOf(\"active_id\"), 1);\n    }\n    if (state.resId && typeof state.resId !== \"number\" && state.resId !== \"new\") {\n        pathKeysToOmit.splice(pathKeysToOmit.indexOf(\"resId\"), 1);\n    }\n    const search = objectToUrlEncodedString(omit(state, ...pathKeysToOmit));\n    const start_url = startUrl();\n    return `/${start_url}${path}${search ? `?${search}` : \"\"}`;\n}\n\nfunction urlToState(urlObj) {\n    const { pathname, hash, search } = urlObj;\n    const state = parseSearchQuery(search);\n\n    // ** url-retrocompatibility **\n    // If the url contains a hash, it can be for two motives:\n    // 1. It is an anchor link, in that case, we ignore it, as it will not have a keys/values format\n    //    the sanitizeHash function will remove it from the hash object.\n    // 2. It has one or more keys/values, in that case, we merge it with the search.\n    if (pathname === \"/web\") {\n        const sanitizedHash = sanitizeHash(parseHash(hash));\n        // Old urls used \"id\", it is now resId for clarity. Remap to the new name.\n        if (sanitizedHash.id) {\n            sanitizedHash.resId = sanitizedHash.id;\n            delete sanitizedHash.id;\n            delete sanitizedHash.view_type;\n        } else if (sanitizedHash.view_type === \"form\") {\n            sanitizedHash.resId = \"new\";\n            delete sanitizedHash.view_type;\n        }\n        Object.assign(state, sanitizedHash);\n        const url = browser.location.origin + router.stateToUrl(state);\n        urlObj.href = url;\n    }\n\n    const [prefix, ...splitPath] = urlObj.pathname.split(\"/\").filter(Boolean);\n\n    if ([\"odoo\", \"scoped_app\"].includes(prefix)) {\n        const actionParts = [...splitPath.entries()].filter(\n            ([_, part]) => !isNumeric(part) && part !== \"new\"\n        );\n        const actions = [];\n        for (const [i, part] of actionParts) {\n            const action = {};\n            const [left, right] = [splitPath[i - 1], splitPath[i + 1]];\n            if (isNumeric(left)) {\n                action.active_id = parseInt(left);\n            }\n\n            if (right === \"new\") {\n                action.resId = \"new\";\n            } else if (isNumeric(right)) {\n                action.resId = parseInt(right);\n            }\n\n            if (part.startsWith(\"action-\")) {\n                // numeric id or xml_id\n                const actionId = part.slice(7);\n                action.action = isNumeric(actionId) ? parseInt(actionId) : actionId;\n            } else if (part.startsWith(\"m-\")) {\n                action.model = part.slice(2);\n            } else if (part.includes(\".\")) {\n                action.model = part;\n            } else {\n                // action tag or path\n                action.action = part;\n            }\n\n            if (action.resId && action.action) {\n                actions.push(omit(action, \"resId\"));\n            }\n            // Don't create actions for models without resId unless they're the last one.\n            // If the last one is a model but doesn't have a view_type, the action service will not mount it anyway.\n            if (action.action || action.resId || i === splitPath.length - 1) {\n                actions.push(action);\n            }\n        }\n        const activeAction = actions.at(-1);\n        if (activeAction) {\n            Object.assign(state, activeAction);\n            state.actionStack = actions;\n        }\n        if (prefix === \"scoped_app\" && !isDisplayStandalone()) {\n            // make sure /scoped_app are redirected to /odoo when using the browser instead of the PWA\n            const url = browser.location.origin + router.stateToUrl(state);\n            urlObj.href = url;\n        }\n    }\n    return state;\n}\n\nlet state;\nlet pushTimeout;\nlet pushArgs;\nlet _lockedKeys;\nlet _hiddenKeysFromUrl = new Set();\n\nexport function startRouter() {\n    const url = new URL(browser.location);\n    state = router.urlToState(url);\n    // ** url-retrocompatibility **\n    if (browser.location.pathname === \"/web\") {\n        // Change the url of the current history entry to the canonical url.\n        // This change should be done only at the first load, and not when clicking on old style internal urls.\n        // Or when clicking back/forward on the browser.\n        browser.history.replaceState(browser.history.state, null, url.href);\n    }\n    pushTimeout = null;\n    pushArgs = {\n        replace: false,\n        reload: false,\n        state: {},\n    };\n    _lockedKeys = new Set([\"debug\", \"lang\"]);\n    _hiddenKeysFromUrl = new Set([...PATH_KEYS, \"actionStack\"]);\n}\n\n/**\n * When the user navigates history using the back/forward button, the browser\n * dispatches a popstate event with the state that was in the history for the\n * corresponding history entry. We just adopt that state so that the webclient\n * can use that previous state without forcing a full page reload.\n */\nbrowser.addEventListener(\"popstate\", (ev) => {\n    browser.clearTimeout(pushTimeout);\n    if (!ev.state) {\n        // We are coming from a click on an anchor.\n        // Add the current state to the history entry so that a future loadstate behaves as expected.\n        browser.history.replaceState({ nextState: state }, \"\", browser.location.href);\n        return;\n    }\n    state = ev.state?.nextState || router.urlToState(new URL(browser.location));\n    // Some client actions want to handle loading their own state. This is a ugly hack to allow not\n    // reloading the webclient's state when they manipulate history.\n    if (!ev.state?.skipRouteChange && !router.skipLoad) {\n        routerBus.trigger(\"ROUTE_CHANGE\");\n    }\n    router.skipLoad = false;\n});\n\n/**\n * When the user navigates the history using the back/forward button, some browsers (Safari iOS and\n * Safari MacOS) can restore the page using the `bfcache` (especially when we come back from an\n * external website). Unfortunately, Odoo wasn't designed to be compatible with this cache, which\n * leads to inconsistencies. When the `bfcache` is used to restore a page, we reload the current\n * page, to be sure that all the elements have been rendered correctly.\n */\nbrowser.addEventListener(\"pageshow\", (ev) => {\n    if (ev.persisted) {\n        browser.clearTimeout(pushTimeout);\n        routerBus.trigger(\"ROUTE_CHANGE\");\n    }\n});\n\n/**\n * When clicking internal links, do a loadState instead of a full page reload.\n * This also alows the mobile app to not open an in-app browser for them.\n */\nbrowser.addEventListener(\"click\", (ev) => {\n    if (ev.defaultPrevented || ev.target.closest(\"[contenteditable]\")) {\n        return;\n    }\n    const a = ev.target.closest(\"a\");\n    const href = a?.getAttribute(\"href\");\n    if (href && !href.startsWith(\"#\")) {\n        let url;\n        try {\n            // ev.target.href is the full url including current path\n            url = new URL(a.href);\n        } catch {\n            return;\n        }\n        if (\n            browser.location.host === url.host &&\n            browser.location.pathname.startsWith(\"/odoo\") &&\n            ([\"/web\", \"/odoo\"].includes(url.pathname) || url.pathname.startsWith(\"/odoo/\")) &&\n            a.target !== \"_blank\"\n        ) {\n            ev.preventDefault();\n            state = router.urlToState(url);\n            if (url.pathname.startsWith(\"/odoo\") && url.hash) {\n                browser.history.pushState({}, \"\", url.href);\n            }\n            new Promise((res) => setTimeout(res, 0)).then(() => routerBus.trigger(\"ROUTE_CHANGE\"));\n        }\n    }\n});\n\n/**\n * @param {string} mode\n */\nfunction makeDebouncedPush(mode) {\n    function doPush() {\n        // Calculates new route based on aggregated search and options\n        const nextState = computeNextState(pushArgs.state, pushArgs.replace);\n        const url = browser.location.origin + router.stateToUrl(nextState);\n        if (!compareUrls(url + browser.location.hash, browser.location.href)) {\n            // If the route changed: pushes or replaces browser state\n            if (mode === \"push\") {\n                // Because doPush is delayed, the history entry will have the wrong name.\n                // We set the document title to what it was at the time of the pushState\n                // call, then push, which generates the history entry with the right title\n                // then restore the title to what it's supposed to be\n                const originalTitle = document.title;\n                document.title = pushArgs.title;\n                browser.history.pushState({ nextState }, \"\", url);\n                document.title = originalTitle;\n            } else {\n                browser.history.replaceState({ nextState }, \"\", url);\n            }\n        } else {\n            // URL didn't change but state might have, update it in place\n            browser.history.replaceState({ nextState }, \"\", browser.location.href);\n        }\n        state = nextState;\n        if (pushArgs.reload) {\n            browser.location.reload();\n        }\n    }\n    /**\n     * @param {object} state\n     * @param {object} options\n     */\n    return function pushOrReplaceState(state, options = {}) {\n        pushArgs.replace ||= options.replace;\n        pushArgs.reload ||= options.reload;\n        pushArgs.title = document.title;\n        Object.assign(pushArgs.state, state);\n        browser.clearTimeout(pushTimeout);\n        const push = () => {\n            doPush();\n            pushTimeout = null;\n            pushArgs = {\n                replace: false,\n                reload: false,\n                state: {},\n            };\n        };\n        if (options.sync) {\n            push();\n        } else {\n            pushTimeout = browser.setTimeout(() => {\n                push();\n            });\n        }\n    };\n}\n\nexport const router = {\n    get current() {\n        return state;\n    },\n    // state <-> url conversions can be patched if needed in a custom webclient.\n    stateToUrl,\n    urlToState,\n    // TODO: stop debouncing these and remove the ugly hack to have the correct title for history entries\n    pushState: makeDebouncedPush(\"push\"),\n    replaceState: makeDebouncedPush(\"replace\"),\n    cancelPushes: () => browser.clearTimeout(pushTimeout),\n    addLockedKey: (key) => _lockedKeys.add(key),\n    hideKeyFromUrl: (key) => _hiddenKeysFromUrl.add(key),\n    skipLoad: false,\n};\n\nstartRouter();\n\nexport function objectToQuery(obj) {\n    const query = {};\n    Object.entries(obj).forEach(([k, v]) => {\n        query[k] = v ? String(v) : v;\n    });\n    return query;\n}\n", "import { registry } from \"../registry\";\n\nexport const titleService = {\n    start() {\n        const titleCounters = {};\n        const titleParts = {};\n\n        function getParts() {\n            return Object.assign({}, titleParts);\n        }\n\n        function setCounters(counters) {\n            for (const key in counters) {\n                const val = counters[key];\n                if (!val) {\n                    delete titleCounters[key];\n                } else {\n                    titleCounters[key] = val;\n                }\n            }\n            updateTitle();\n        }\n\n        function setParts(parts) {\n            for (const key in parts) {\n                const val = parts[key];\n                if (!val) {\n                    delete titleParts[key];\n                } else {\n                    titleParts[key] = val;\n                }\n            }\n            updateTitle();\n        }\n\n        function updateTitle() {\n            const counter = Object.values(titleCounters).reduce((acc, count) => acc + count, 0);\n            const name = Object.values(titleParts).join(\" - \") || \"Odoo\";\n            if (!counter) {\n                document.title = name;\n            } else {\n                document.title = `(${counter}) ${name}`;\n            }\n        }\n\n        return {\n            /**\n             * @returns {string}\n             */\n            get current() {\n                return document.title;\n            },\n            getParts,\n            setCounters,\n            setParts,\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"title\", titleService);\n", "import { useHotkey } from \"../hotkeys/hotkey_hook\";\n\nimport { Component, useRef } from \"@odoo/owl\";\n\n/**\n * Custom checkbox\n *\n * <CheckBox\n *    value=\"boolean\"\n *    disabled=\"boolean\"\n *    onChange=\"_onValueChange\"\n * >\n *    Change the label text\n * </CheckBox>\n *\n * @extends Component\n */\n\nexport class CheckBox extends Component {\n    static template = \"web.CheckBox\";\n    static nextId = 1;\n    static defaultProps = {\n        onChange: () => {},\n    };\n    static props = {\n        id: {\n            type: true,\n            optional: true,\n        },\n        disabled: {\n            type: Boolean,\n            optional: true,\n        },\n        value: {\n            type: Boolean,\n            optional: true,\n        },\n        slots: {\n            type: Object,\n            optional: true,\n        },\n        onChange: {\n            type: Function,\n            optional: true,\n        },\n        className: {\n            type: String,\n            optional: true,\n        },\n        name: {\n            type: String,\n            optional: true,\n        },\n        indeterminate: {\n            type: Boolean,\n            optional: true,\n        },\n    };\n\n    setup() {\n        this.id = `checkbox-comp-${CheckBox.nextId++}`;\n        this.rootRef = useRef(\"root\");\n\n        // Make it toggleable through the Enter hotkey\n        // when the focus is inside the root element\n        useHotkey(\n            \"Enter\",\n            ({ area }) => {\n                const oldValue = area.querySelector(\"input\").checked;\n                this.props.onChange(!oldValue);\n            },\n            { area: () => this.rootRef.el, bypassEditableProtection: true }\n        );\n    }\n\n    onClick(ev) {\n        if (ev.composedPath().find((el) => [\"INPUT\", \"LABEL\"].includes(el.tagName))) {\n            // The onChange will handle these cases.\n            ev.stopPropagation();\n            return;\n        }\n\n        // Reproduce the click event behavior as if it comes from the input element.\n        const input = this.rootRef.el.querySelector(\"input\");\n        input.focus();\n        if (!this.props.disabled) {\n            ev.stopPropagation();\n            input.checked = !input.checked;\n            this.props.onChange(input.checked);\n        }\n    }\n\n    onChange(ev) {\n        if (!this.props.disabled) {\n            this.props.onChange(ev.target.checked);\n        }\n    }\n}\n", "import { Component, onMounted, onWillStart, useEffect, useRef, useState, status } from \"@odoo/owl\";\nimport { loadBundle } from \"@web/core/assets\";\n\nexport class CodeEditor extends Component {\n    static template = \"web.CodeEditor\";\n    static components = {};\n    static props = {\n        mode: {\n            type: String,\n            optional: true,\n            validate: (mode) => CodeEditor.MODES.includes(mode),\n        },\n        value: { validate: (v) => typeof v === \"string\", optional: true },\n        readonly: { type: Boolean, optional: true },\n        onChange: { type: Function, optional: true },\n        onBlur: { type: Function, optional: true },\n        class: { type: String, optional: true },\n        theme: {\n            type: String,\n            optional: true,\n            validate: (theme) => CodeEditor.THEMES.includes(theme),\n        },\n        maxLines: { type: Number, optional: true },\n        sessionId: { type: [Number, String], optional: true },\n        initialCursorPosition: { type: Object, optional: true },\n        showLineNumbers: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        readonly: false,\n        value: \"\",\n        onChange: () => {},\n        class: \"\",\n        theme: \"\",\n        sessionId: 1,\n        showLineNumbers: true,\n    };\n\n    static MODES = [\"javascript\", \"xml\", \"qweb\", \"scss\", \"python\"];\n    static THEMES = [\"\", \"monokai\"];\n\n    setup() {\n        this.editorRef = useRef(\"editorRef\");\n        this.state = useState({\n            activeMode: undefined,\n        });\n\n        onWillStart(async () => await loadBundle(\"web.ace_lib\"));\n\n        const sessions = {};\n        // The ace library triggers the \"change\" event even if the change is\n        // programmatic. Even worse, it triggers 2 \"change\" events in that case,\n        // one with the empty string, and one with the new value. We only want\n        // to notify the parent of changes done by the user, in the UI, so we\n        // use this flag to filter out noisy \"change\" events.\n        let ignoredAceChange = false;\n        useEffect(\n            (el) => {\n                if (!el) {\n                    return;\n                }\n\n                // keep in closure\n                const aceEditor = window.ace.edit(el);\n                this.aceEditor = aceEditor;\n\n                this.aceEditor.setOptions({\n                    maxLines: this.props.maxLines,\n                    showPrintMargin: false,\n                    useWorker: false,\n                });\n                this.aceEditor.$blockScrolling = true;\n\n                this.aceEditor.on(\"changeMode\", () => {\n                    this.state.activeMode = this.aceEditor.getSession().$modeId.split(\"/\").at(-1);\n                });\n\n                const session = aceEditor.getSession();\n                if (!sessions[this.props.sessionId]) {\n                    sessions[this.props.sessionId] = session;\n                }\n                session.setValue(this.props.value);\n                session.on(\"change\", () => {\n                    if (this.props.onChange && !ignoredAceChange) {\n                        this.props.onChange(\n                            this.aceEditor.getValue(),\n                            this.aceEditor.getCursorPosition()\n                        );\n                    }\n                });\n                this.aceEditor.on(\"blur\", () => {\n                    if (this.props.onBlur) {\n                        this.props.onBlur();\n                    }\n                });\n\n                return () => {\n                    aceEditor.destroy();\n                };\n            },\n            () => [this.editorRef.el]\n        );\n\n        useEffect(\n            (theme) => this.aceEditor.setTheme(theme ? `ace/theme/${theme}` : \"\"),\n            () => [this.props.theme]\n        );\n\n        useEffect(\n            (readonly, showLineNumbers) => {\n                this.aceEditor.setOptions({\n                    readOnly: readonly,\n                    highlightActiveLine: !readonly,\n                    highlightGutterLine: !readonly,\n                });\n\n                this.aceEditor.renderer.setOptions({\n                    displayIndentGuides: !readonly,\n                    showGutter: !readonly && showLineNumbers,\n                });\n\n                this.aceEditor.renderer.$cursorLayer.element.style.display = readonly\n                    ? \"none\"\n                    : \"block\";\n            },\n            () => [this.props.readonly, this.props.showLineNumbers]\n        );\n\n        useEffect(\n            (sessionId, mode, value) => {\n                let session = sessions[sessionId];\n                if (session) {\n                    if (session.getValue() !== value) {\n                        ignoredAceChange = true;\n                        session.setValue(value);\n                        ignoredAceChange = false;\n                    }\n                } else {\n                    session = new window.ace.EditSession(value);\n                    session.setUndoManager(new window.ace.UndoManager());\n                    session.setOptions({\n                        useWorker: false,\n                        tabSize: 2,\n                        useSoftTabs: true,\n                    });\n                    session.on(\"change\", () => {\n                        if (this.props.onChange && !ignoredAceChange) {\n                            this.props.onChange(\n                                this.aceEditor.getValue(),\n                                this.aceEditor.getCursorPosition()\n                            );\n                        }\n                    });\n                    sessions[sessionId] = session;\n                }\n                session.setMode(mode ? `ace/mode/${mode}` : \"\");\n                this.aceEditor.setSession(session);\n            },\n            () => [this.props.sessionId, this.props.mode, this.props.value]\n        );\n\n        const initialCursorPosition = this.props.initialCursorPosition;\n        if (initialCursorPosition) {\n            onMounted(() => {\n                // Wait for ace to be fully operational\n                window.requestAnimationFrame(() => {\n                    if (status(this) != \"destroyed\" && this.aceEditor) {\n                        this.aceEditor.focus();\n                        const { row, column } = initialCursorPosition;\n                        const pos = {\n                            row: row || 0,\n                            column: column || 0,\n                        };\n                        this.aceEditor.selection.moveToPosition(pos);\n                        this.aceEditor.renderer.scrollCursorIntoView(pos, 0.5);\n                    }\n                });\n            });\n        }\n    }\n}\n", "import { Component, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { CustomColorPicker } from \"@web/core/color_picker/custom_color_picker/custom_color_picker\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { isCSSColor, isColorGradient, normalizeCSSColor } from \"@web/core/utils/colors\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { POSITION_BUS } from \"../position/position_hook\";\nimport { registry } from \"../registry\";\n\n// These colors are already normalized as per normalizeCSSColor in @web/legacy/js/widgets/colorpicker\nexport const DEFAULT_COLORS = [\n    [\"#000000\", \"#424242\", \"#636363\", \"#9C9C94\", \"#CEC6CE\", \"#EFEFEF\", \"#F7F7F7\", \"#FFFFFF\"],\n    [\"#FF0000\", \"#FF9C00\", \"#FFFF00\", \"#00FF00\", \"#00FFFF\", \"#0000FF\", \"#9C00FF\", \"#FF00FF\"],\n    [\"#F7C6CE\", \"#FFE7CE\", \"#FFEFC6\", \"#D6EFD6\", \"#CEDEE7\", \"#CEE7F7\", \"#D6D6E7\", \"#E7D6DE\"],\n    [\"#E79C9C\", \"#FFC69C\", \"#FFE79C\", \"#B5D6A5\", \"#A5C6CE\", \"#9CC6EF\", \"#B5A5D6\", \"#D6A5BD\"],\n    [\"#E76363\", \"#F7AD6B\", \"#FFD663\", \"#94BD7B\", \"#73A5AD\", \"#6BADDE\", \"#8C7BC6\", \"#C67BA5\"],\n    [\"#CE0000\", \"#E79439\", \"#EFC631\", \"#6BA54A\", \"#4A7B8C\", \"#3984C6\", \"#634AA5\", \"#A54A7B\"],\n    [\"#9C0000\", \"#B56308\", \"#BD9400\", \"#397B21\", \"#104A5A\", \"#085294\", \"#311873\", \"#731842\"],\n    [\"#630000\", \"#7B3900\", \"#846300\", \"#295218\", \"#083139\", \"#003163\", \"#21104A\", \"#4A1031\"],\n];\n\nexport const DEFAULT_GRAYSCALES = {\n    solid: [\"black\", \"900\", \"800\", \"600\", \"400\", \"200\", \"100\", \"white\"],\n};\n\n// These CSS variables are defined in html_editor.\n// Using ColorPicker without html_editor installed is extremely unlikely.\nexport const DEFAULT_THEME_COLOR_VARS = [\n    \"o-color-1\",\n    \"o-color-2\",\n    \"o-color-3\",\n    \"o-color-4\",\n    \"o-color-5\",\n];\n\nexport class ColorPicker extends Component {\n    static template = \"web.ColorPicker\";\n    static components = { CustomColorPicker };\n    static props = {\n        state: {\n            type: Object,\n            shape: {\n                selectedColor: String,\n                selectedColorCombination: { type: String, optional: true },\n                getTargetedElements: { type: Function, optional: true },\n                defaultTab: String,\n                selectedTab: { type: String, optional: true },\n                // todo: remove the `mode` prop in master\n                mode: { type: String, optional: true },\n            },\n        },\n        getUsedCustomColors: Function,\n        applyColor: Function,\n        applyColorPreview: Function,\n        applyColorResetPreview: Function,\n        editColorCombination: { type: Function, optional: true },\n        setOnCloseCallback: { type: Function, optional: true },\n        setOperationCallbacks: { type: Function, optional: true },\n        enabledTabs: { type: Array, optional: true },\n        colorPrefix: { type: String },\n        cssVarColorPrefix: { type: String, optional: true },\n        defaultOpacity: { type: Number, optional: true },\n        grayscales: { type: Object, optional: true },\n        noTransparency: { type: Boolean, optional: true },\n        close: { type: Function, optional: true },\n        className: { type: String, optional: true },\n    };\n    static defaultProps = {\n        close: () => {},\n        defaultOpacity: 100,\n        enabledTabs: [\"solid\", \"custom\"],\n        cssVarColorPrefix: \"\",\n        setOnCloseCallback: () => {},\n    };\n\n    setup() {\n        this.tabs = registry\n            .category(\"color_picker_tabs\")\n            .getAll()\n            .filter((tab) => this.props.enabledTabs.includes(tab.id));\n        this.root = useRef(\"root\");\n\n        this.DEFAULT_COLORS = DEFAULT_COLORS;\n        this.grayscales = Object.assign({}, DEFAULT_GRAYSCALES, this.props.grayscales);\n        this.DEFAULT_THEME_COLOR_VARS = DEFAULT_THEME_COLOR_VARS;\n        this.defaultColorSet = this.getDefaultColorSet();\n        this.defaultColor = this.props.state.selectedColor;\n        this.focusedBtn = null;\n        this.onApplyCallback = () => {};\n        this.onPreviewRevertCallback = () => {};\n        this.getPreviewColor = () => {};\n\n        this.state = useState({\n            activeTab: this.props.state.selectedTab || this.getDefaultTab(),\n            currentCustomColor: this.props.state.selectedColor,\n            currentColorPreview: undefined,\n            showGradientPicker: false,\n        });\n        this.usedCustomColors = this.props.getUsedCustomColors();\n        useEffect(\n            () => {\n                // Recompute the positioning of the popover if any.\n                this.env[POSITION_BUS]?.trigger(\"update\");\n            },\n            () => [this.state.activeTab]\n        );\n    }\n\n    getDefaultTab() {\n        if (this.props.enabledTabs.includes(this.props.state.defaultTab)) {\n            return this.props.state.defaultTab;\n        }\n        return this.props.enabledTabs[0];\n    }\n\n    get selectedColor() {\n        return this.props.state.selectedColor;\n    }\n\n    get isDarkTheme() {\n        return cookie.get(\"color_scheme\") === \"dark\";\n    }\n\n    setTab(tab) {\n        this.state.activeTab = tab;\n        // Reset the preview revert callback, as it is tab-specific.\n        this.setOperationCallbacks({ onPreviewRevertCallback: () => {} });\n        this.applyColorResetPreview();\n    }\n\n    processColorFromEvent(ev) {\n        const target = this.getTarget(ev);\n        let color = target.dataset.color || \"\";\n        if (color && isColorCombination(color)) {\n            return color;\n        }\n        if (color && !isCSSColor(color) && !isColorGradient(color)) {\n            color = this.props.colorPrefix + color;\n        }\n        return color;\n    }\n    /**\n     * @param {Object} cbs - callbacks\n     * @param {Function} cbs.onApplyCallback\n     * @param {Function} cbs.onPreviewRevertCallback\n     */\n    setOperationCallbacks(cbs) {\n        // The gradient colorpicker has a nested ColorPicker. We need to use the\n        // `setOperationCallbacks` from the parent ColorPicker for it to be\n        // impacted.\n        if (this.props.setOperationCallbacks) {\n            this.props.setOperationCallbacks(cbs);\n        }\n        if (cbs.onApplyCallback) {\n            this.onApplyCallback = cbs.onApplyCallback;\n        }\n        if (cbs.onPreviewRevertCallback) {\n            this.onPreviewRevertCallback = cbs.onPreviewRevertCallback;\n        }\n        if (cbs.getPreviewColor) {\n            this.getPreviewColor = cbs.getPreviewColor;\n        }\n    }\n\n    applyColor(color) {\n        this.state.currentCustomColor = color;\n        this.props.applyColor(color);\n        this.defaultColorSet = this.getDefaultColorSet();\n        this.onApplyCallback();\n    }\n\n    onColorApply(ev) {\n        if (!this.isColorButton(this.getTarget(ev))) {\n            return;\n        }\n        const color = this.processColorFromEvent(ev);\n        this.applyColor(color);\n        this.props.close();\n    }\n\n    applyColorResetPreview() {\n        this.props.applyColorResetPreview();\n        this.state.currentColorPreview = undefined;\n        this.onPreviewRevertCallback();\n    }\n\n    onColorPreview(ev) {\n        const color = ev.hex || ev.gradient || this.processColorFromEvent(ev);\n        this.props.applyColorPreview(color);\n        this.state.currentColorPreview = this.getPreviewColor();\n    }\n\n    onColorHover(ev) {\n        if (!this.isColorButton(this.getTarget(ev))) {\n            return;\n        }\n        this.onColorPreview(ev);\n    }\n\n    onColorHoverOut(ev) {\n        if (!this.isColorButton(this.getTarget(ev))) {\n            return;\n        }\n        this.applyColorResetPreview();\n    }\n    getTarget(ev) {\n        const target = ev.target.closest(`[data-color]`);\n        return this.root.el.contains(target) ? target : ev.target;\n    }\n\n    onColorFocusin(ev) {\n        // In the editor color picker, the preview and reset reapply the\n        // selection, which can remove the focus from the current button (if the\n        // node is recreated). We need to force the focus and break the infinite\n        // loop that it could trigger.\n        if (this.focusedBtn === ev.target) {\n            this.focusedBtn = null;\n            return;\n        }\n        this.focusedBtn = ev.target;\n        this.onColorHover(ev);\n        if (document.activeElement !== ev.target) {\n            // The focus was lost during revert. Reset it where it should be.\n            ev.target.focus();\n        }\n    }\n\n    onColorFocusout(ev) {\n        if (!ev.relatedTarget || !this.isColorButton(ev.relatedTarget)) {\n            // Do not trigger a revert if we are in the focus loop (i.e. focus\n            // a button > selection is reset > focusout). Otherwise, the\n            // relatedTarget should always be one of the colorpicker's buttons.\n            return;\n        }\n        const activeEl = document.activeElement;\n        this.applyColorResetPreview();\n        if (document.activeElement !== activeEl) {\n            // The focus was lost during revert. Reset it where it should be.\n            ev.relatedTarget.focus();\n        }\n    }\n\n    getDefaultColorSet() {\n        if (!this.props.state.selectedColor) {\n            return;\n        }\n        let defaultColors = this.props.enabledTabs.includes(\"solid\")\n            ? this.DEFAULT_THEME_COLOR_VARS\n            : [];\n        for (const grayscale of Object.values(this.grayscales)) {\n            defaultColors = defaultColors.concat(grayscale);\n        }\n\n        const targetedElement =\n            this.props.state.getTargetedElements?.()[0] || document.documentElement;\n        const selectedColor = this.props.state.selectedColor.toUpperCase();\n        const htmlStyle =\n            targetedElement.ownerDocument.defaultView.getComputedStyle(targetedElement);\n\n        for (const color of defaultColors) {\n            const cssVar = normalizeCSSColor(htmlStyle.getPropertyValue(`--${color}`));\n            if (cssVar?.toUpperCase() === selectedColor) {\n                return color;\n            }\n        }\n\n        return false;\n    }\n\n    colorPickerNavigation(ev) {\n        const { target, key } = ev;\n        if (!target.classList.contains(\"o_color_button\")) {\n            return;\n        }\n        if (![\"ArrowRight\", \"ArrowLeft\", \"ArrowUp\", \"ArrowDown\"].includes(key)) {\n            return;\n        }\n\n        let targetBtn;\n        if (key === \"ArrowRight\") {\n            targetBtn = target.nextElementSibling;\n        } else if (key === \"ArrowLeft\") {\n            targetBtn = target.previousElementSibling;\n        } else if (key === \"ArrowUp\" || key === \"ArrowDown\") {\n            const buttonIndex = [...target.parentElement.children].indexOf(target);\n            const nbColumns = getComputedStyle(target).getPropertyValue(\n                \"--o-color-picker-grid-columns\"\n            );\n            targetBtn =\n                target.parentElement.children[\n                    buttonIndex + (key === \"ArrowUp\" ? -1 : 1) * nbColumns\n                ];\n            if (!targetBtn) {\n                const row =\n                    key === \"ArrowUp\"\n                        ? target.parentElement.previousElementSibling\n                        : target.parentElement.nextElementSibling;\n                if (row?.matches(\".o_color_section, .o_colorpicker_section\")) {\n                    targetBtn = row.children[buttonIndex];\n                }\n            }\n        }\n        if (targetBtn && targetBtn.classList.contains(\"o_color_button\")) {\n            targetBtn.focus();\n        }\n    }\n\n    isColorButton(targetEl) {\n        return targetEl.tagName === \"BUTTON\" && !targetEl.matches(\".o_colorpicker_ignore\");\n    }\n}\n\nexport function useColorPicker(refName, props, options = {}) {\n    // Callback to be overridden by child components (e.g. custom color picker).\n    let onCloseCallback = () => {};\n    const setOnCloseCallback = (cb) => {\n        onCloseCallback = cb;\n    };\n    props.setOnCloseCallback = setOnCloseCallback;\n    if (options.onClose) {\n        const onClose = options.onClose;\n        options.onClose = () => {\n            onCloseCallback();\n            onClose();\n        };\n    }\n\n    const colorPicker = usePopover(ColorPicker, options);\n    const root = useRef(refName);\n\n    function onClick() {\n        colorPicker.isOpen ? colorPicker.close() : colorPicker.open(root.el, props);\n    }\n\n    useEffect(\n        (el) => {\n            if (!el) {\n                return;\n            }\n            el.addEventListener(\"click\", onClick);\n            return () => {\n                el.removeEventListener(\"click\", onClick);\n            };\n        },\n        () => [root.el]\n    );\n\n    return colorPicker;\n}\n\n/**\n * Checks if a given string is a color combination.\n *\n * @param {string} color\n * @returns {boolean}\n */\nfunction isColorCombination(color) {\n    return color.startsWith(\"o_cc\");\n}\n", "import { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport {\n    convertCSSColorToRgba,\n    convertHslToRgb,\n    convertRgbaToCSSColor,\n    convertRgbToHsl,\n} from \"@web/core/utils/colors\";\nimport { uniqueId } from \"@web/core/utils/functions\";\nimport { clamp } from \"@web/core/utils/numbers\";\nimport { debounce, useThrottleForAnimation } from \"@web/core/utils/timing\";\n\nimport { Component, onMounted, onWillUpdateProps, useExternalListener, useRef } from \"@odoo/owl\";\n\nconst ARROW_KEYS = [\"arrowup\", \"arrowdown\", \"arrowleft\", \"arrowright\"];\nconst SLIDER_KEYS = [...ARROW_KEYS, \"pageup\", \"pagedown\", \"home\", \"end\"];\n\nconst DEFAULT_COLOR = \"#FF0000\";\n\nexport class CustomColorPicker extends Component {\n    static template = \"web.CustomColorPicker\";\n    static props = {\n        document: { type: true, optional: true },\n        defaultColor: { type: String, optional: true },\n        selectedColor: { type: String, optional: true },\n        noTransparency: { type: Boolean, optional: true },\n        stopClickPropagation: { type: Boolean, optional: true },\n        onColorSelect: { type: Function, optional: true },\n        onColorPreview: { type: Function, optional: true },\n        onInputEnter: { type: Function, optional: true },\n        defaultOpacity: { type: Number, optional: true },\n        setOnCloseCallback: { type: Function, optional: true },\n        setOperationCallbacks: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        document: window.document,\n        defaultColor: DEFAULT_COLOR,\n        defaultOpacity: 100,\n        noTransparency: false,\n        stopClickPropagation: false,\n        onColorSelect: () => {},\n        onColorPreview: () => {},\n        onInputEnter: () => {},\n    };\n\n    setup() {\n        this.pickerFlag = false;\n        this.sliderFlag = false;\n        this.opacitySliderFlag = false;\n        if (this.props.defaultOpacity > 0 && this.props.defaultOpacity <= 1) {\n            this.props.defaultOpacity *= 100;\n        }\n        if (this.props.defaultColor.length <= 7) {\n            const opacityHex = Math.round((this.props.defaultOpacity / 100) * 255)\n                .toString(16)\n                .padStart(2, \"0\");\n            this.props.defaultColor += opacityHex;\n        }\n        this.colorComponents = {};\n        this.uniqueId = uniqueId(\"colorpicker\");\n        this.selectedHexValue = \"\";\n        this.shouldSetSelectedColor = false;\n        this.lastFocusedSliderEl = undefined;\n        if (!this.props.selectedColor) {\n            this.props.selectedColor = this.props.defaultColor;\n        }\n        this.debouncedOnChangeInputs = debounce(this.onChangeInputs.bind(this), 10, true);\n\n        this.elRef = useRef(\"el\");\n        this.colorPickerAreaRef = useRef(\"colorPickerArea\");\n        this.colorPickerPointerRef = useRef(\"colorPickerPointer\");\n        this.colorSliderRef = useRef(\"colorSlider\");\n        this.colorSliderPointerRef = useRef(\"colorSliderPointer\");\n        this.opacitySliderRef = useRef(\"opacitySlider\");\n        this.opacitySliderPointerRef = useRef(\"opacitySliderPointer\");\n\n        // Need to be bound on all documents to work in all possible cases (we\n        // have to be able to start dragging/moving from the colorpicker to\n        // anywhere on the screen, crossing iframes).\n        const documents = [\n            window.top,\n            ...Array.from(window.top.frames).filter((frame) => {\n                try {\n                    const document = frame.document;\n                    return !!document;\n                } catch {\n                    // We cannot access the document (cross origin).\n                    return false;\n                }\n            }),\n        ].map((w) => w.document);\n        this.throttleOnPointerMove = useThrottleForAnimation((ev) => {\n            this.onPointerMovePicker(ev);\n            this.onPointerMoveSlider(ev);\n            this.onPointerMoveOpacitySlider(ev);\n        });\n\n        for (const doc of documents) {\n            useExternalListener(doc, \"pointermove\", this.throttleOnPointerMove);\n            useExternalListener(doc, \"pointerup\", this.onPointerUp.bind(this));\n            useExternalListener(doc, \"keydown\", this.onEscapeKeydown.bind(this), { capture: true });\n        }\n        // Apply the previewed custom color when the popover is closed.\n        this.props.setOnCloseCallback?.(() => {\n            if (this.shouldSetSelectedColor) {\n                this._colorSelected();\n            }\n        });\n        this.props.setOperationCallbacks?.({\n            getPreviewColor: () => {\n                if (this.shouldSetSelectedColor) {\n                    return this.colorComponents.hex;\n                }\n            },\n            onApplyCallback: () => {\n                this.shouldSetSelectedColor = false;\n            },\n            // Reapply the current custom color preview after reverting a preview.\n            // Typical usecase: 1) modify the custom color, 2) hover one of the\n            // black-white tints, 3) hover out.\n            onPreviewRevertCallback: () => {\n                if (this.previewActive && this.shouldSetSelectedColor) {\n                    this.props.onColorPreview(this.colorComponents);\n                }\n            },\n        });\n        onMounted(async () => {\n            const rgba =\n                convertCSSColorToRgba(this.props.selectedColor) ||\n                convertCSSColorToRgba(this.props.defaultColor);\n            if (rgba) {\n                this._updateRgba(rgba.red, rgba.green, rgba.blue, rgba.opacity);\n            }\n\n            this.previewActive = true;\n            this._updateUI();\n        });\n        onWillUpdateProps((newProps) => {\n            const newSelectedColor = newProps.selectedColor\n                ? newProps.selectedColor\n                : newProps.defaultColor;\n            this.setSelectedColor(newSelectedColor);\n        });\n    }\n\n    /**\n     * Sets the currently selected color\n     *\n     * @param {string} color rgb[a]\n     */\n    setSelectedColor(color) {\n        const rgba = convertCSSColorToRgba(color);\n        if (rgba) {\n            const oldPreviewActive = this.previewActive;\n            this.previewActive = false;\n            this._updateRgba(rgba.red, rgba.green, rgba.blue, rgba.opacity);\n            this.previewActive = oldPreviewActive;\n            this._updateUI();\n        }\n    }\n    /**\n     * @param {string[]} allowedKeys\n     * @returns {string[]} allowed keys + modifiers\n     */\n    getAllowedHotkeys(allowedKeys) {\n        return allowedKeys.flatMap((key) => [key, `control+${key}`]);\n    }\n    /**\n     * @param {HTMLElement} el\n     */\n    setLastFocusedSliderEl(el) {\n        this.lastFocusedSliderEl = el;\n        document.activeElement.blur();\n    }\n\n    get el() {\n        return this.elRef.el;\n    }\n    /**\n     * @param {string} hotkey\n     * @param {number} value\n     * @param {Object} [options]\n     * @param {number} [options.min=0]\n     * @param {number} [options.max=100]\n     * @param {number} [options.defaultStep=10] - default step\n     * @param {number} [options.modifierStep=1] - step when holding ctrl+key\n     * @param {number} [options.leap=20] - step for pageup / pagedown\n     * @returns {number} updated and clamped value\n     */\n    handleRangeKeydownValue(\n        hotkey,\n        value,\n        { min = 0, max = 100, defaultStep = 10, modifierStep = 1, leap = 20 } = {}\n    ) {\n        let step = defaultStep;\n        if (hotkey.startsWith(\"control+\")) {\n            step = modifierStep;\n        }\n        const mainKey = hotkey.replace(\"control+\", \"\");\n        if (mainKey === \"pageup\" || mainKey === \"pagedown\") {\n            step = leap;\n        }\n        if ([\"arrowup\", \"arrowright\", \"pageup\"].includes(mainKey)) {\n            value += step;\n        } else if ([\"arrowdown\", \"arrowleft\", \"pagedown\"].includes(mainKey)) {\n            value -= step;\n        } else if (mainKey === \"home\") {\n            value = min;\n        } else if (mainKey === \"end\") {\n            value = max;\n        }\n        return clamp(value, min, max);\n    }\n    /**\n     * Selects and applies a currently previewed color if \"Enter\" was pressed.\n     *\n     * @param {String} hotkey\n     */\n    selectColorOnEnter(hotkey) {\n        if (hotkey === \"enter\" && this.shouldSetSelectedColor) {\n            this.pickerFlag = false;\n            this.sliderFlag = false;\n            this.opacitySliderFlag = false;\n            this._colorSelected();\n        }\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Updates input values, color preview, picker and slider pointer positions.\n     *\n     * @private\n     */\n    _updateUI() {\n        // Update inputs\n        for (const [color, value] of Object.entries(this.colorComponents)) {\n            const input = this.el.querySelector(`.o_${color}_input`);\n            if (input) {\n                input.value = value;\n            }\n        }\n\n        // Update picker area and picker pointer position\n        const colorPickerArea = this.colorPickerAreaRef.el;\n        colorPickerArea.style.backgroundColor = `hsl(${this.colorComponents.hue}, 100%, 50%)`;\n        const top = ((100 - this.colorComponents.lightness) * colorPickerArea.clientHeight) / 100;\n        const left = (this.colorComponents.saturation * colorPickerArea.clientWidth) / 100;\n\n        const colorpickerPointer = this.colorPickerPointerRef.el;\n        colorpickerPointer.style.top = top - 5 + \"px\";\n        colorpickerPointer.style.left = left - 5 + \"px\";\n        colorpickerPointer.setAttribute(\n            \"aria-label\",\n            _t(\"Saturation: %(saturationLvl)s %. Brightness: %(brightnessLvl)s %\", {\n                saturationLvl: this.colorComponents.saturation?.toFixed(2) || \"0\",\n                brightnessLvl: this.colorComponents.lightness?.toFixed(2) || \"0\",\n            })\n        );\n\n        // Update color slider position\n        const colorSlider = this.colorSliderRef.el;\n        const height = colorSlider.clientHeight;\n        const y = (this.colorComponents.hue * height) / 360;\n        this.colorSliderPointerRef.el.style.bottom = `${Math.round(y - 4)}px`;\n        this.colorSliderPointerRef.el.setAttribute(\n            \"aria-valuenow\",\n            this.colorComponents.hue.toFixed(2)\n        );\n\n        if (!this.props.noTransparency) {\n            // Update opacity slider position\n            const opacitySlider = this.opacitySliderRef.el;\n            const heightOpacity = opacitySlider.clientHeight;\n            const z = heightOpacity * (1 - this.colorComponents.opacity / 100.0);\n            this.opacitySliderPointerRef.el.style.top = `${Math.round(z - 2)}px`;\n            this.opacitySliderPointerRef.el.setAttribute(\n                \"aria-valuenow\",\n                this.colorComponents.opacity.toFixed(2)\n            );\n\n            // Add gradient color on opacity slider\n            const sliderColor = this.colorComponents.hex.slice(0, 7);\n            opacitySlider.style.background = `linear-gradient(${sliderColor} 0%, transparent 100%)`;\n        }\n    }\n    /**\n     * Updates colors according to given hex value. Opacity is left unchanged.\n     *\n     * @private\n     * @param {string} hex - hexadecimal code\n     */\n    _updateHex(hex) {\n        const rgb = convertCSSColorToRgba(hex);\n        if (!rgb) {\n            return;\n        }\n        Object.assign(\n            this.colorComponents,\n            { hex: hex },\n            rgb,\n            convertRgbToHsl(rgb.red, rgb.green, rgb.blue)\n        );\n        this._updateCssColor();\n    }\n    /**\n     * Updates colors according to given RGB values.\n     *\n     * @private\n     * @param {integer} r\n     * @param {integer} g\n     * @param {integer} b\n     * @param {integer} [a]\n     */\n    _updateRgba(r, g, b, a) {\n        // Remove full transparency in case some lightness is added\n        const opacity = a || this.colorComponents.opacity;\n        if (opacity < 0.1 && (r > 0.1 || g > 0.1 || b > 0.1)) {\n            a = this.props.defaultOpacity;\n        }\n\n        const hex = convertRgbaToCSSColor(r, g, b, a);\n        if (!hex) {\n            return;\n        }\n        Object.assign(\n            this.colorComponents,\n            { red: r, green: g, blue: b },\n            a === undefined ? {} : { opacity: a },\n            { hex: hex },\n            convertRgbToHsl(r, g, b)\n        );\n        this._updateCssColor();\n    }\n    /**\n     * Updates colors according to given HSL values.\n     *\n     * @private\n     * @param {integer} h\n     * @param {integer} s\n     * @param {integer} l\n     */\n    _updateHsl(h, s, l) {\n        // Remove full darkness/brightness and non-saturation in case hue is changed\n        if (0.1 < Math.abs(h - this.colorComponents.hue)) {\n            if (l < 0.1 || 99.9 < l) {\n                l = 50;\n            }\n            if (s < 0.1) {\n                s = 100;\n            }\n        }\n        // Remove full transparency in case some lightness is added\n        let a = this.colorComponents.opacity;\n        if (a < 0.1 && l > 0.1) {\n            a = this.props.defaultOpacity;\n        }\n\n        const rgb = convertHslToRgb(h, s, l);\n        if (!rgb) {\n            return;\n        }\n        // We receive an hexa as we ignore the opacity\n        const hex = convertRgbaToCSSColor(rgb.red, rgb.green, rgb.blue, a);\n        Object.assign(\n            this.colorComponents,\n            { hue: h, saturation: s, lightness: l },\n            rgb,\n            { hex: hex },\n            { opacity: a }\n        );\n        this._updateCssColor();\n    }\n    /**\n     * Updates color opacity.\n     *\n     * @private\n     * @param {integer} a\n     */\n    _updateOpacity(a) {\n        if (a < 0 || a > 100) {\n            return;\n        }\n        Object.assign(this.colorComponents, { opacity: a });\n        const r = this.colorComponents.red;\n        const g = this.colorComponents.green;\n        const b = this.colorComponents.blue;\n        Object.assign(this.colorComponents, { hex: convertRgbaToCSSColor(r, g, b, a) });\n        this._updateCssColor();\n    }\n    /**\n     * Trigger an event to annonce that the widget value has changed\n     *\n     * @private\n     */\n    _colorSelected() {\n        this.props.onColorSelect(this.colorComponents);\n    }\n    /**\n     * Updates css color representation.\n     *\n     * @private\n     */\n    _updateCssColor() {\n        const r = this.colorComponents.red;\n        const g = this.colorComponents.green;\n        const b = this.colorComponents.blue;\n        const a = this.colorComponents.opacity;\n        Object.assign(this.colorComponents, { cssColor: convertRgbaToCSSColor(r, g, b, a) });\n        if (this.previewActive) {\n            this.props.onColorPreview(this.colorComponents);\n        }\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    onKeydown(ev) {\n        if (ev.key === \"Enter\") {\n            if (ev.target.tagName === \"INPUT\") {\n                this.onChangeInputs(ev);\n            }\n            ev.preventDefault();\n            this.props.onInputEnter(ev);\n        }\n    }\n    /**\n     * @param {Event} ev\n     */\n    onClick(ev) {\n        if (this.props.stopClickPropagation) {\n            ev.stopPropagation();\n        }\n        //TODO: we should remove it with legacy web_editor\n        ev.__isColorpickerClick = true;\n\n        if (ev.target.dataset.colorMethod === \"hex\" && !this.selectedHexValue) {\n            ev.target.select();\n            this.selectedHexValue = ev.target.value;\n            return;\n        }\n        this.selectedHexValue = \"\";\n    }\n    onPointerUp() {\n        if (this.pickerFlag || this.sliderFlag || this.opacitySliderFlag) {\n            this.shouldSetSelectedColor = true;\n            this._updateCssColor();\n        }\n        this.pickerFlag = false;\n        this.sliderFlag = false;\n        this.opacitySliderFlag = false;\n\n        if (this.lastFocusedSliderEl) {\n            this.lastFocusedSliderEl.focus();\n            this.lastFocusedSliderEl = undefined;\n        }\n    }\n    /**\n     * Removes the close callback on Escape, so that a preview is cancelled with\n     * escape instead of being applied.\n     *\n     * @param {KeydownEvent} ev\n     */\n    onEscapeKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        if (hotkey === \"escape\") {\n            this.props.setOnCloseCallback?.(() => {});\n        }\n    }\n    /**\n     * Updates color when the user starts clicking on the picker.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPointerDownPicker(ev) {\n        this.pickerFlag = true;\n        ev.preventDefault();\n        this.onPointerMovePicker(ev);\n        this.setLastFocusedSliderEl(this.colorPickerPointerRef.el);\n    }\n    /**\n     * Updates saturation and lightness values on pointer drag over picker.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPointerMovePicker(ev) {\n        if (!this.pickerFlag) {\n            return;\n        }\n\n        const colorPickerArea = this.colorPickerAreaRef.el;\n        const rect = colorPickerArea.getClientRects()[0];\n        const top = ev.pageY - rect.top;\n        const left = ev.pageX - rect.left;\n        let saturation = Math.round((100 * left) / colorPickerArea.clientWidth);\n        let lightness = Math.round(\n            (100 * (colorPickerArea.clientHeight - top)) / colorPickerArea.clientHeight\n        );\n        saturation = clamp(saturation, 0, 100);\n        lightness = clamp(lightness, 0, 100);\n\n        this._updateHsl(this.colorComponents.hue, saturation, lightness);\n        this._updateUI();\n    }\n    /**\n     * Updates saturation and lightness values on arrow keydown over picker.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPickerKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        this.selectColorOnEnter(hotkey);\n        if (!this.getAllowedHotkeys(ARROW_KEYS).includes(hotkey)) {\n            return;\n        }\n        let saturation = this.colorComponents.saturation;\n        let lightness = this.colorComponents.lightness;\n        let step = 10;\n        if (hotkey.startsWith(\"control+\")) {\n            step = 1;\n        }\n        const mainKey = hotkey.replace(\"control+\", \"\");\n        if (mainKey === \"arrowup\") {\n            lightness += step;\n        } else if (mainKey === \"arrowdown\") {\n            lightness -= step;\n        } else if (mainKey === \"arrowright\") {\n            saturation += step;\n        } else if (mainKey === \"arrowleft\") {\n            saturation -= step;\n        }\n        lightness = clamp(lightness, 0, 100);\n        saturation = clamp(saturation, 0, 100);\n\n        this._updateHsl(this.colorComponents.hue, saturation, lightness);\n        this._updateUI();\n        this.shouldSetSelectedColor = true;\n    }\n    /**\n     * Updates color when user starts clicking on slider.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPointerDownSlider(ev) {\n        this.sliderFlag = true;\n        ev.preventDefault();\n        this.onPointerMoveSlider(ev);\n        this.setLastFocusedSliderEl(this.colorSliderPointerRef.el);\n    }\n    /**\n     * Updates hue value on pointer drag over slider.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPointerMoveSlider(ev) {\n        if (!this.sliderFlag) {\n            return;\n        }\n\n        const colorSlider = this.colorSliderRef.el;\n        const colorSliderRects = colorSlider.getClientRects();\n        const y = colorSliderRects[0].height - (ev.pageY - colorSliderRects[0].top);\n        let hue = Math.round((360 * y) / colorSlider.clientHeight);\n        hue = clamp(hue, 0, 360);\n\n        this._updateHsl(hue, this.colorComponents.saturation, this.colorComponents.lightness);\n        this._updateUI();\n    }\n    /**\n     * Updates hue value on arrow keydown on slider.\n     *\n     * @param {Event} ev\n     */\n    onSliderKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        this.selectColorOnEnter(hotkey);\n        if (!this.getAllowedHotkeys(SLIDER_KEYS).includes(hotkey)) {\n            return;\n        }\n        const hue = this.handleRangeKeydownValue(hotkey, this.colorComponents.hue, {\n            min: 0,\n            max: 360,\n            leap: 30,\n        });\n        this._updateHsl(hue, this.colorComponents.saturation, this.colorComponents.lightness);\n        this._updateUI();\n        this.shouldSetSelectedColor = true;\n    }\n    /**\n     * Updates opacity when user starts clicking on opacity slider.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPointerDownOpacitySlider(ev) {\n        this.opacitySliderFlag = true;\n        ev.preventDefault();\n        this.onPointerMoveOpacitySlider(ev);\n        this.setLastFocusedSliderEl(this.opacitySliderPointerRef.el);\n    }\n    /**\n     * Updates opacity value on pointer drag over opacity slider.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onPointerMoveOpacitySlider(ev) {\n        if (!this.opacitySliderFlag || this.props.noTransparency) {\n            return;\n        }\n\n        const opacitySlider = this.opacitySliderRef.el;\n        const y = ev.pageY - opacitySlider.getClientRects()[0].top;\n        let opacity = Math.round(100 * (1 - y / opacitySlider.clientHeight));\n        opacity = clamp(opacity, 0, 100);\n\n        this._updateOpacity(opacity);\n        this._updateUI();\n    }\n    /**\n     * Updates opacity value on arrow keydown on opacity slider.\n     *\n     * @param {Event} ev\n     */\n    onOpacitySliderKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        this.selectColorOnEnter(hotkey);\n        if (!this.getAllowedHotkeys(SLIDER_KEYS).includes(hotkey)) {\n            return;\n        }\n        const opacity = this.handleRangeKeydownValue(hotkey, this.colorComponents.opacity);\n\n        this._updateOpacity(opacity);\n        this._updateUI();\n        this.shouldSetSelectedColor = true;\n    }\n    /**\n     * Called when input value is changed -> Updates UI: Set picker and slider\n     * position and set colors.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onChangeInputs(ev) {\n        switch (ev.target.dataset.colorMethod) {\n            case \"hex\":\n                // Handled by the \"input\" event (see \"onHexColorInput\").\n                return;\n            case \"hsl\":\n                this._updateHsl(\n                    parseInt(this.el.querySelector(\".o_hue_input\").value),\n                    parseInt(this.el.querySelector(\".o_saturation_input\").value),\n                    parseInt(this.el.querySelector(\".o_lightness_input\").value)\n                );\n                break;\n        }\n        this._updateUI();\n        this._colorSelected();\n    }\n    /**\n     * Called when the hex color input's input event is triggered.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    onHexColorInput(ev) {\n        const hexColorValue = ev.target.value.replaceAll(\"#\", \"\");\n        if (hexColorValue.length === 6 || hexColorValue.length === 8) {\n            this._updateHex(`#${hexColorValue}`);\n            this._updateUI();\n            this._colorSelected();\n        }\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { isColorGradient } from \"@web/core/utils/colors\";\nimport { CustomColorPicker } from \"../custom_color_picker/custom_color_picker\";\n\nexport class ColorPickerCustomTab extends Component {\n    static template = \"web.ColorPickerCustomTab\";\n    static components = { CustomColorPicker };\n    static props = {\n        applyColor: Function,\n        colorPickerNavigation: Function,\n        onColorClick: Function,\n        onColorPreview: Function,\n        onColorPointerOver: Function,\n        onColorPointerOut: Function,\n        onFocusin: Function,\n        onFocusout: Function,\n        getUsedCustomColors: { type: Function, optional: true },\n        currentColorPreview: { type: String, optional: true },\n        currentCustomColor: { type: String, optional: true },\n        defaultColorSet: { type: String | Boolean, optional: true },\n        defaultOpacity: { type: Number, optional: true },\n        grayscales: { type: Object, optional: true },\n        cssVarColorPrefix: { type: String, optional: true },\n        noTransparency: { type: Boolean, optional: true },\n        setOnCloseCallback: { type: Function, optional: true },\n        setOperationCallbacks: { type: Function, optional: true },\n        \"*\": { optional: true },\n    };\n\n    setup() {\n        this.usedCustomColors = this.props.getUsedCustomColors();\n    }\n\n    isValidCustomColor(color) {\n        return color && color.slice(7, 9) !== \"00\" && !isColorGradient(color);\n    }\n}\n\nregistry.category(\"color_picker_tabs\").add(\"web.custom\", {\n    id: \"custom\",\n    name: _t(\"Custom\"),\n    component: ColorPickerCustomTab,\n});\n", "import { Component } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\n\nexport class ColorPickerSolidTab extends Component {\n    static template = \"web.ColorPickerSolidTab\";\n    static props = {\n        colorPickerNavigation: Function,\n        onColorClick: Function,\n        onColorPointerOver: Function,\n        onColorPointerOut: Function,\n        onFocusin: Function,\n        onFocusout: Function,\n        currentCustomColor: { type: String, optional: true },\n        defaultColorSet: { type: String | Boolean, optional: true },\n        cssVarColorPrefix: { type: String, optional: true },\n        defaultColors: Array,\n        defaultThemeColorVars: Array,\n        \"*\": { optional: true },\n    };\n}\n\nregistry.category(\"color_picker_tabs\").add(\"web.solid\", {\n    id: \"solid\",\n    name: _t(\"Solid\"),\n    component: ColorPickerSolidTab,\n});\n", "import { _t } from \"@web/core/l10n/translation\";\n\nimport { Component, useRef, useState, useExternalListener } from \"@odoo/owl\";\n\nexport class ColorList extends Component {\n    static COLORS = [\n        _t(\"No color\"),\n        _t(\"Red\"),\n        _t(\"Orange\"),\n        _t(\"Yellow\"),\n        _t(\"Cyan\"),\n        _t(\"Purple\"),\n        _t(\"Almond\"),\n        _t(\"Teal\"),\n        _t(\"Blue\"),\n        _t(\"Raspberry\"),\n        _t(\"Green\"),\n        _t(\"Violet\"),\n    ];\n    static template = \"web.ColorList\";\n    static defaultProps = {\n        forceExpanded: false,\n        isExpanded: false,\n    };\n    static props = {\n        canToggle: { type: Boolean, optional: true },\n        colors: Array,\n        forceExpanded: { type: Boolean, optional: true },\n        isExpanded: { type: Boolean, optional: true },\n        onColorSelected: Function,\n        selectedColor: { type: Number, optional: true },\n    };\n\n    setup() {\n        this.colorlistRef = useRef(\"colorlist\");\n        this.state = useState({ isExpanded: this.props.isExpanded });\n        useExternalListener(window, \"click\", this.onOutsideClick);\n    }\n    get colors() {\n        return this.constructor.COLORS;\n    }\n    onColorSelected(id) {\n        this.props.onColorSelected(id);\n        if (!this.props.forceExpanded) {\n            this.state.isExpanded = false;\n        }\n    }\n    onOutsideClick(ev) {\n        if (this.colorlistRef.el.contains(ev.target) || this.props.forceExpanded) {\n            return;\n        }\n        this.state.isExpanded = false;\n    }\n    onToggle(ev) {\n        if (this.props.canToggle) {\n            ev.preventDefault();\n            ev.stopPropagation();\n            this.state.isExpanded = !this.state.isExpanded;\n            this.colorlistRef.el.firstElementChild.focus();\n        }\n    }\n}\n", "import { clamp } from \"@web/core/utils/numbers\";\n/**\n * Lists of colors that contrast well with each other to be used in various\n * visualizations (eg. graphs/charts), both in bright and dark themes.\n */\n\nconst COLORS_ENT_BRIGHT = [\"#875A7B\", \"#A5D8D7\", \"#DCD0D9\"];\nconst COLORS_ENT_DARK = [\"#6B3E66\", \"#147875\", \"#5A395A\"];\nconst COLORS_SM = [\n    \"#4EA7F2\", // Blue\n    \"#EA6175\", // Red\n    \"#43C5B1\", // Teal\n    \"#F4A261\", // Orange\n    \"#8481DD\", // Purple\n    \"#FFD86D\", // Yellow\n];\nconst COLORS_MD = [\n    \"#4EA7F2\", // Blue #1\n    \"#3188E6\", // Blue #2\n    \"#43C5B1\", // Teal #1\n    \"#00A78D\", // Teal #2\n    \"#EA6175\", // Red #1\n    \"#CE4257\", // Red #2\n    \"#F4A261\", // Orange #1\n    \"#F48935\", // Orange #2\n    \"#8481DD\", // Purple #1\n    \"#5752D1\", // Purple #2\n    \"#FFD86D\", // Yellow #1\n    \"#FFBC2C\", // Yellow #2\n];\nconst COLORS_LG = [\n    \"#4EA7F2\", // Blue #1\n    \"#3188E6\", // Blue #2\n    \"#056BD9\", // Blue #3\n    \"#A76DBC\", // Violet #1\n    \"#7F4295\", // Violet #2\n    \"#6D2387\", // Violet #3\n    \"#EA6175\", // Red #1\n    \"#CE4257\", // Red #2\n    \"#982738\", // Red #3\n    \"#43C5B1\", // Teal #1\n    \"#00A78D\", // Teal #2\n    \"#0E8270\", // Teal #3\n    \"#F4A261\", // Orange #1\n    \"#F48935\", // Orange #2\n    \"#BE5D10\", // Orange #3\n    \"#8481DD\", // Purple #1\n    \"#5752D1\", // Purple #2\n    \"#3A3580\", // Purple #3\n    \"#A4A8B6\", // Gray #1\n    \"#7E8290\", // Gray #2\n    \"#545B70\", // Gray #3\n    \"#FFD86D\", // Yellow #1\n    \"#FFBC2C\", // Yellow #2\n    \"#C08A16\", // Yellow #3\n];\nconst COLORS_XL = [\n    \"#4EA7F2\", // Blue #1\n    \"#3188E6\", // Blue #2\n    \"#056BD9\", // Blue #3\n    \"#155193\", // Blue #4\n    \"#A76DBC\", // Violet #1\n    \"#7F4295\", // Violet #1\n    \"#6D2387\", // Violet #1\n    \"#4F1565\", // Violet #1\n    \"#EA6175\", // Red #1\n    \"#CE4257\", // Red #2\n    \"#982738\", // Red #3\n    \"#791B29\", // Red #4\n    \"#43C5B1\", // Teal #1\n    \"#00A78D\", // Teal #2\n    \"#0E8270\", // Teal #3\n    \"#105F53\", // Teal #4\n    \"#F4A261\", // Orange #1\n    \"#F48935\", // Orange #2\n    \"#BE5D10\", // Orange #3\n    \"#7D380D\", // Orange #4\n    \"#8481DD\", // Purple #1\n    \"#5752D1\", // Purple #2\n    \"#3A3580\", // Purple #3\n    \"#26235F\", // Purple #4\n    \"#A4A8B6\", // Grey #1\n    \"#7E8290\", // Grey #2\n    \"#545B70\", // Grey #3\n    \"#3F4250\", // Grey #4\n    \"#FFD86D\", // Yellow #1\n    \"#FFBC2C\", // Yellow #2\n    \"#C08A16\", // Yellow #3\n    \"#936A12\", // Yellow #4\n];\n\n/**\n * @param {string} colorScheme\n * @param {string} paletteName\n * @returns {array}\n */\nexport function getColors(colorScheme, paletteName) {\n    switch (paletteName) {\n        case \"odoo\":\n            return colorScheme === \"dark\" ? COLORS_ENT_DARK : COLORS_ENT_BRIGHT;\n        case \"sm\":\n            return COLORS_SM;\n        case \"md\":\n            return COLORS_MD;\n        case \"lg\":\n            return COLORS_LG;\n        default:\n            return COLORS_XL;\n    }\n}\n\n/**\n * @param {number} index\n * @param {string} colorScheme\n * @returns {string}\n */\nexport function getColor(index, colorScheme, paletteSizeOrName) {\n    let paletteName;\n    if (paletteSizeOrName === \"odoo\") {\n        paletteName = \"odoo\";\n    } else if (paletteSizeOrName <= 6 || paletteSizeOrName === \"sm\") {\n        paletteName = \"sm\";\n    } else if (paletteSizeOrName <= 12 || paletteSizeOrName === \"md\") {\n        paletteName = \"md\";\n    } else if (paletteSizeOrName <= 24 || paletteSizeOrName === \"lg\") {\n        paletteName = \"lg\";\n    } else {\n        paletteName = \"xl\";\n    }\n    const colors = getColors(colorScheme, paletteName);\n    return colors[index % colors.length];\n}\n\nexport const DEFAULT_BG = \"#d3d3d3\";\n\nexport function getBorderWhite(colorScheme) {\n    return colorScheme === \"dark\" ? \"rgba(38, 42, 54, .2)\" : \"rgba(249,250,251, .2)\";\n}\n\nconst RGB_REGEX = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i;\n\n/**\n * @param {string} hex\n * @param {number} opacity\n * @returns {string}\n */\nexport function hexToRGBA(hex, opacity) {\n    const rgb = RGB_REGEX.exec(hex)\n        .slice(1, 4)\n        .map((n) => parseInt(n, 16))\n        .join(\",\");\n    return `rgba(${rgb},${opacity})`;\n}\n\n/**\n * Used to return custom colors depending on the color scheme\n * @param {string} colorScheme\n * @param {string} brightModeColor\n * @param {string} darkModeColor\n * @returns {string|Number|Boolean}\n */\n\nexport function getCustomColor(colorScheme, brightModeColor, darkModeColor) {\n    if (darkModeColor === undefined) {\n        return brightModeColor;\n    } else {\n        return colorScheme === \"dark\" ? darkModeColor : brightModeColor;\n    }\n}\n\n/**\n * Used to lighten a color\n * @param {string} color\n * @param {number} factor\n * @returns {string}\n */\nexport function lightenColor(color, factor) {\n    factor = clamp(factor, 0, 1);\n\n    let r = parseInt(color.substring(1, 3), 16);\n    let g = parseInt(color.substring(3, 5), 16);\n    let b = parseInt(color.substring(5, 7), 16);\n\n    r = Math.round(r + (255 - r) * factor);\n    g = Math.round(g + (255 - g) * factor);\n    b = Math.round(b + (255 - b) * factor);\n\n    r = r.toString(16).padStart(2, \"0\");\n    g = g.toString(16).padStart(2, \"0\");\n    b = b.toString(16).padStart(2, \"0\");\n\n    return `#${r}${g}${b}`;\n}\n\n/**\n * Used to darken a color\n * @param {string} color\n * @param {number} factor\n * @returns {string}\n */\nexport function darkenColor(color, factor) {\n    factor = clamp(factor, 0, 1);\n\n    let r = parseInt(color.substring(1, 3), 16);\n    let g = parseInt(color.substring(3, 5), 16);\n    let b = parseInt(color.substring(5, 7), 16);\n\n    r = Math.round(r * (1 - factor));\n    g = Math.round(g * (1 - factor));\n    b = Math.round(b * (1 - factor));\n\n    r = r.toString(16).padStart(2, \"0\");\n    g = g.toString(16).padStart(2, \"0\");\n    b = b.toString(16).padStart(2, \"0\");\n\n    return `#${r}${g}${b}`;\n}\n", "import { Dialog } from \"../dialog/dialog\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nimport { Component } from \"@odoo/owl\";\n\nexport const deleteConfirmationMessage = _t(\n    `Ready to make your record disappear into thin air? Are you sure?\nIt will be gone forever!\n\nThink twice before you click that 'Delete' button!`\n);\n\nexport class ConfirmationDialog extends Component {\n    static template = \"web.ConfirmationDialog\";\n    static components = { Dialog };\n    static props = {\n        close: Function,\n        title: {\n            validate: (m) => {\n                return (\n                    typeof m === \"string\" ||\n                    (typeof m === \"object\" && typeof m.toString === \"function\")\n                );\n            },\n            optional: true,\n        },\n        body: { type: String, optional: true },\n        confirm: { type: Function, optional: true },\n        confirmLabel: { type: String, optional: true },\n        confirmClass: { type: String, optional: true },\n        cancel: { type: Function, optional: true },\n        cancelLabel: { type: String, optional: true },\n        dismiss: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        confirmLabel: _t(\"Ok\"),\n        cancelLabel: _t(\"Cancel\"),\n        confirmClass: \"btn-primary\",\n        title: _t(\"Confirmation\"),\n    };\n\n    setup() {\n        this.env.dialogData.dismiss = () => this._dismiss();\n        this.modalRef = useChildRef();\n        this.isProcess = false;\n    }\n\n    async _cancel() {\n        return this.execButton(this.props.cancel);\n    }\n\n    async _confirm() {\n        return this.execButton(this.props.confirm);\n    }\n\n    async _dismiss() {\n        return this.execButton(this.props.dismiss || this.props.cancel);\n    }\n\n    setButtonsDisabled(disabled) {\n        this.isProcess = disabled;\n        if (!this.modalRef.el) {\n            return; // safety belt for stable versions\n        }\n        for (const button of [...this.modalRef.el.querySelectorAll(\".modal-footer button\")]) {\n            button.disabled = disabled;\n        }\n    }\n\n    async execButton(callback) {\n        if (this.isProcess) {\n            return;\n        }\n        this.setButtonsDisabled(true);\n        if (callback) {\n            let shouldClose;\n            try {\n                shouldClose = await callback();\n            } catch (e) {\n                this.props.close();\n                throw e;\n            }\n            if (shouldClose === false) {\n                this.setButtonsDisabled(false);\n                return;\n            }\n        }\n        this.props.close();\n    }\n}\n\nexport class AlertDialog extends ConfirmationDialog {\n    static template = \"web.AlertDialog\";\n    static props = {\n        ...ConfirmationDialog.props,\n        contentClass: { type: String, optional: true },\n    };\n    static defaultProps = {\n        ...ConfirmationDialog.defaultProps,\n        title: _t(\"Alert\"),\n    };\n}\n", "import { evaluateExpr, parseExpr } from \"./py_js/py\";\nimport { BUILTINS } from \"./py_js/py_builtin\";\nimport { evaluate } from \"./py_js/py_interpreter\";\n\n/**\n * @typedef {{\n *  lang?: string;\n *  tz?: string;\n *  uid?: number | false;\n *  [key: string]: any;\n * }} Context\n * @typedef {Context | string | undefined} ContextDescription\n */\n\n/**\n * Create an evaluated context from an arbitrary list of context representations.\n * The evaluated context in construction is used along the way to evaluate further parts.\n *\n * @param {ContextDescription[]} contexts\n * @param {Context} [initialEvaluationContext] optional evaluation context to start from.\n * @returns {Context}\n */\nexport function makeContext(contexts, initialEvaluationContext) {\n    const evaluationContext = Object.assign({}, initialEvaluationContext);\n    const context = {};\n    for (let ctx of contexts) {\n        if (ctx !== \"\") {\n            ctx = typeof ctx === \"string\" ? evaluateExpr(ctx, evaluationContext) : ctx;\n            Object.assign(context, ctx);\n            Object.assign(evaluationContext, context); // is this behavior really wanted ?\n        }\n    }\n    return context;\n}\n\n/**\n * Extract a partial list of variable names found in the AST.\n * Note that it is not complete. It is used as an heuristic to avoid\n * evaluating expressions that we know for sure will fail.\n *\n * @param {AST} ast\n * @returns string[]\n */\nfunction getPartialNames(ast) {\n    if (ast.type === 5) {\n        return [ast.value];\n    }\n    if (ast.type === 6) {\n        return getPartialNames(ast.right);\n    }\n    if (ast.type === 14 || ast.type === 7) {\n        return getPartialNames(ast.left).concat(getPartialNames(ast.right));\n    }\n    if (ast.type === 15) {\n        return getPartialNames(ast.obj);\n    }\n    return [];\n}\n\n/**\n * Allow to evaluate a context with an incomplete evaluation context. The evaluated context only\n * contains keys whose values are static or can be evaluated with the given evaluation context.\n *\n * @param {string} context\n * @param {Context} [evaluationContext={}]\n * @returns {Context}\n */\nexport function evalPartialContext(_context, evaluationContext = {}) {\n    const ast = parseExpr(_context);\n    const context = {};\n    for (const key in ast.value) {\n        const value = ast.value[key];\n        if (\n            getPartialNames(value).some((name) => !(name in evaluationContext || name in BUILTINS))\n        ) {\n            continue;\n        }\n        try {\n            context[key] = evaluate(value, evaluationContext);\n        } catch {\n            // ignore this key as we can't evaluate its value\n        }\n    }\n    return context;\n}\n", "import { browser } from \"@web/core/browser/browser\";\nimport { Tooltip } from \"@web/core/tooltip/tooltip\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { Component, useRef } from \"@odoo/owl\";\n\nexport class CopyButton extends Component {\n    static template = \"web.CopyButton\";\n    static props = {\n        className: { type: String, optional: true },\n        copyText: { type: String, optional: true },\n        disabled: { type: Boolean, optional: true },\n        successText: { type: String, optional: true },\n        icon: { type: String, optional: true },\n        content: { type: [String, Object, Function], optional: true },\n    };\n\n    setup() {\n        this.button = useRef(\"button\");\n        this.popover = usePopover(Tooltip);\n    }\n\n    showTooltip() {\n        this.popover.open(this.button.el, { tooltip: this.props.successText });\n        browser.setTimeout(this.popover.close, 800);\n    }\n\n    async onClick() {\n        let write, content;\n        if (typeof this.props.content === \"function\") {\n            content = this.props.content();\n        } else {\n            content = this.props.content;\n        }\n        // any kind of content can be copied into the clipboard using\n        // the appropriate native methods\n        if (typeof content === \"string\" || content instanceof String) {\n            write = (value) => browser.navigator.clipboard.writeText(value);\n        } else {\n            write = (value) => browser.navigator.clipboard.write(value);\n        }\n        try {\n            await write(content);\n        } catch (error) {\n            return browser.console.warn(error);\n        }\n        this.showTooltip();\n    }\n}\n", "import { reactive } from \"@odoo/owl\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { user } from \"@web/core/user\";\nimport { formatFloat, humanNumber } from \"@web/core/utils/numbers\";\nimport { nbsp } from \"@web/core/utils/strings\";\nimport { session } from \"@web/session\";\n\nexport const currencies = session.currencies || {};\n// to make sure code is reading currencies from here\ndelete session.currencies;\n\nexport function getCurrency(id) {\n    return currencies[id];\n}\n\nexport async function getCurrencyRates() {\n    const rates = reactive({});\n\n    function recordsToRates(records) {\n        return Object.fromEntries(records.map((r) => [r.id, r.inverse_rate]));\n    }\n\n    const model = \"res.currency\";\n    const method = \"read\";\n    const url = `/web/dataset/call_kw/${model}/${method}`;\n    const context = {\n        ...user.context,\n        to_currency: user.activeCompany.currency_id,\n    };\n    const params = {\n        model,\n        method,\n        args: [Object.keys(currencies).map(Number), [\"inverse_rate\"]],\n        kwargs: { context },\n    };\n    const records = await rpc(url, params, {\n        cache: {\n            type: \"disk\",\n            update: \"once\",\n            callback: (records, hasChanged) => {\n                if (hasChanged) {\n                    Object.assign(rates, recordsToRates(records));\n                }\n            },\n        },\n    });\n    Object.assign(rates, recordsToRates(records));\n    return rates;\n}\n\n/**\n * Returns a string representing a monetary value. The result takes into account\n * the user settings (to display the correct decimal separator, currency, ...).\n *\n * @param {number} value the value that should be formatted\n * @param {number} [currencyId] the id of the 'res.currency' to use\n * @param {Object} [options]\n *   additional options to override the values in the python description of the\n *   field.\n * @param {Object} [options.data] a mapping of field names to field values,\n *   required with options.currencyField\n * @param {boolean} [options.noSymbol] this currency has not a sympbol\n * @param {boolean} [options.humanReadable] if true, large numbers are formatted\n *   to a human readable format.\n * @param {number} [options.minDigits] see @humanNumber\n * @param {boolean} [options.trailingZeros] if false, numbers will have zeros\n *  to the right of the last non-zero digit hidden\n * @param {[number, number]} [options.digits] the number of digits that should\n *   be used, instead of the default digits precision in the field.  The first\n *   number is always ignored (legacy constraint)\n * @returns {string}\n */\nexport function formatCurrency(amount, currencyId, options = {}) {\n    const currency = getCurrency(currencyId);\n    const digits = options.digits || (currency && currency.digits);\n\n    let formattedAmount;\n    if (options.humanReadable) {\n        formattedAmount = humanNumber(amount, {\n            decimals: digits ? digits[1] : 2,\n            minDigits: options.minDigits,\n        });\n    } else {\n        formattedAmount = formatFloat(amount, { digits, trailingZeros: options.trailingZeros });\n    }\n\n    if (!currency || options.noSymbol) {\n        return formattedAmount;\n    }\n    const formatted = [currency.symbol, formattedAmount];\n    if (currency.position === \"after\") {\n        formatted.reverse();\n    }\n    return formatted.join(nbsp);\n}\n", "import { Component } from \"@odoo/owl\";\nimport { omit } from \"../utils/objects\";\nimport { DateTimePicker } from \"./datetime_picker\";\nimport { useDateTimePicker } from \"./datetime_picker_hook\";\n\n/**\n * @typedef {import(\"./datetime_picker\").DateTimePickerProps & {\n *  format?: string;\n *  id?: string;\n *  onApply?: (value: DateTime) => any;\n *  onChange?: (value: DateTime) => any;\n *  placeholder?: string;\n * }} DateTimeInputProps\n */\n\nconst dateTimeInputOwnProps = {\n    format: { type: String, optional: true },\n    id: { type: String, optional: true },\n    class: { type: String, optional: true },\n    onChange: { type: Function, optional: true },\n    onApply: { type: Function, optional: true },\n    placeholder: { type: String, optional: true },\n    disabled: { type: Boolean, optional: true },\n};\n\n/** @extends {Component<DateTimeInputProps>} */\nexport class DateTimeInput extends Component {\n    static props = {\n        ...DateTimePicker.props,\n        ...dateTimeInputOwnProps,\n    };\n\n    static template = \"web.DateTimeInput\";\n\n    setup() {\n        const getPickerProps = () => omit(this.props, ...Object.keys(dateTimeInputOwnProps));\n\n        useDateTimePicker({\n            format: this.props.format,\n            showSeconds: this.props.rounding <= 0,\n            get pickerProps() {\n                return getPickerProps();\n            },\n            onApply: (...args) => this.props.onApply?.(...args),\n            onChange: (...args) => this.props.onChange?.(...args),\n        });\n    }\n}\n", "import { Component, onWillRender, onWillUpdateProps, useState } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { MAX_VALID_DATE, MIN_VALID_DATE, clampDate, isInRange, today } from \"../l10n/dates\";\nimport { localization } from \"../l10n/localization\";\nimport { ensureArray } from \"../utils/arrays\";\nimport { TimePicker } from \"@web/core/time_picker/time_picker\";\nimport { Time } from \"@web/core/l10n/time\";\n\nconst { DateTime, Info } = luxon;\n\n/**\n * @typedef DateItem\n * @property {string} id\n * @property {boolean} includesToday\n * @property {boolean} isOutOfRange\n * @property {boolean} isValid\n * @property {string} label\n * @property {DateRange} range\n * @property {string} extraClass\n *\n * @typedef {\"today\" | NullableDateTime} DateLimit\n *\n * @typedef {[DateTime, DateTime]} DateRange\n *\n * @typedef {luxon[\"DateTime\"][\"prototype\"]} DateTime\n *\n * @typedef DateTimePickerProps\n * @property {number} [focusedDateIndex=0]\n * @property {boolean} [showWeekNumbers=true]\n * @property {DaysOfWeekFormat} [daysOfWeekFormat=\"narrow\"]\n * @property {DateLimit} [maxDate]\n * @property {PrecisionLevel} [maxPrecision=\"decades\"]\n * @property {DateLimit} [minDate]\n * @property {PrecisionLevel} [minPrecision=\"days\"]\n * @property {() => any} [onReset]\n * @property {(value: DateTime | DateRange, unit: \"date\" | \"time\") => any} [onSelect]\n * @property {() => any} [onToggleRange]\n * @property {boolean} [range]\n * @property {number} [rounding=5] the rounding in minutes, pass 0 to show seconds, pass 1 to avoid\n *  rounding minutes without displaying seconds.\n * @property {() => boolean} [showRangeToggler]\n * @property {{ buttons?: any }} [slots]\n * @property {\"date\" | \"datetime\"} [type]\n * @property {NullableDateTime | NullableDateRange} [value]\n * @property {(date: DateTime) => boolean} [isDateValid]\n * @property {(date: DateTime) => string} [dayCellClass]\n *\n * @typedef {DateItem | MonthItem} Item\n *\n * @typedef MonthItem\n * @property {[string, string][]} daysOfWeek\n * @property {string} id\n * @property {number} number\n * @property {WeekItem[]} weeks\n *\n * @typedef {import(\"@web/core/l10n/dates\").NullableDateTime} NullableDateTime\n *\n * @typedef {import(\"@web/core/l10n/dates\").NullableDateRange} NullableDateRange\n *\n * @typedef PrecisionInfo\n * @property {(date: DateTime, params: Partial<DateTimePickerProps>) => string} getTitle\n * @property {(date: DateTime, params: Partial<DateTimePickerProps>) => Item[]} getItems\n * @property {string} mainTitle\n * @property {string} nextTitle\n * @property {string} prevTitle\n * @property {Record<string, number>} step\n *\n * @typedef {\"days\" | \"months\" | \"years\" | \"decades\"} PrecisionLevel\n *\n * @typedef {\"short\" | \"narrow\"} DaysOfWeekFormat\n *\n * @typedef WeekItem\n * @property {DateItem[]} days\n * @property {number} number\n */\n\n/**\n * @param {DateTime} date\n */\nconst getStartOfDecade = (date) => Math.floor(date.year / 10) * 10;\n\n/**\n * @param {DateTime} date\n */\nconst getStartOfCentury = (date) => Math.floor(date.year / 100) * 100;\n\n/**\n * @param {DateTime} date\n */\nconst getStartOfWeek = (date) => {\n    const { weekStart } = localization;\n    return date.set({ weekday: date.weekday < weekStart ? weekStart - 7 : weekStart });\n};\n\n/**\n * @param {number} min\n * @param {number} max\n */\nconst numberRange = (min, max) => [...Array(max - min)].map((_, i) => i + min);\n\n/**\n * @param {NullableDateTime | \"today\"} value\n * @param {NullableDateTime | \"today\"} defaultValue\n */\nconst parseLimitDate = (value, defaultValue) =>\n    clampDate(value === \"today\" ? today() : value || defaultValue, MIN_VALID_DATE, MAX_VALID_DATE);\n\n/**\n * @param {Object} params\n * @param {boolean} [params.isOutOfRange=false]\n * @param {boolean} [params.isValid=true]\n * @param {keyof DateTime} params.label\n * @param {string} [params.extraClass]\n * @param {[DateTime, DateTime]} params.range\n * @returns {DateItem}\n */\nconst toDateItem = ({ isOutOfRange = false, isValid = true, label, range, extraClass }) => ({\n    id: range[0].toISODate(),\n    includesToday: isInRange(today(), range),\n    isOutOfRange,\n    isValid,\n    label: String(range[0][label]),\n    range,\n    extraClass,\n});\n\n/**\n * @param {DateItem[]} weekDayItems\n * @returns {WeekItem}\n */\nconst toWeekItem = (weekDayItems) => ({\n    number: weekDayItems[3].range[0].weekNumber,\n    days: weekDayItems,\n});\n\n/**\n * Precision levels\n * @type {Map<PrecisionLevel, PrecisionInfo>}\n */\nconst PRECISION_LEVELS = new Map()\n    .set(\"days\", {\n        mainTitle: _t(\"Select month\"),\n        nextTitle: _t(\"Next month\"),\n        prevTitle: _t(\"Previous month\"),\n        step: { month: 1 },\n        getTitle: (date) => `${date.monthLong} ${date.year}`,\n        getItems: (date, { maxDate, minDate, showWeekNumbers, isDateValid, dayCellClass }) => {\n            const startDates = [date];\n\n            /** @type {WeekItem[]} */\n            const lastWeeks = [];\n            let shouldAddLastWeek = false;\n\n            const dayItems = startDates.map((date, i) => {\n                const monthRange = [date.startOf(\"month\"), date.endOf(\"month\")];\n                /** @type {WeekItem[]} */\n                const weeks = [];\n\n                // Generate 6 weeks for current month\n                let startOfNextWeek = getStartOfWeek(monthRange[0]);\n                for (let w = 0; w < WEEKS_PER_MONTH; w++) {\n                    const weekDayItems = [];\n                    // Generate all days of the week\n                    for (let d = 0; d < DAYS_PER_WEEK; d++) {\n                        const day = startOfNextWeek.plus({ day: d });\n                        const range = [day, day.endOf(\"day\")];\n                        const dayItem = toDateItem({\n                            isOutOfRange: !isInRange(day, monthRange),\n                            isValid: isInRange(range, [minDate, maxDate]) && isDateValid?.(day),\n                            label: \"day\",\n                            range,\n                            extraClass: dayCellClass?.(day) || \"\",\n                        });\n                        weekDayItems.push(dayItem);\n                        if (d === DAYS_PER_WEEK - 1) {\n                            startOfNextWeek = day.plus({ day: 1 });\n                        }\n                        if (w === WEEKS_PER_MONTH - 1) {\n                            shouldAddLastWeek = true;\n                        }\n                    }\n\n                    const weekItem = toWeekItem(weekDayItems);\n                    if (w === WEEKS_PER_MONTH - 1) {\n                        lastWeeks.push(weekItem);\n                    } else {\n                        weeks.push(weekItem);\n                    }\n                }\n\n                // Generate days of week labels\n                const daysOfWeek = weeks[0].days.map((d) => [\n                    d.range[0].weekdayShort,\n                    d.range[0].weekdayLong,\n                    Info.weekdays(\"narrow\", { locale: d.range[0].locale })[d.range[0].weekday - 1],\n                ]);\n                if (showWeekNumbers) {\n                    daysOfWeek.unshift([\"\", _t(\"Week numbers\"), \"\"]);\n                }\n\n                return {\n                    id: `__month__${i}`,\n                    number: monthRange[0].month,\n                    daysOfWeek,\n                    weeks,\n                };\n            });\n\n            if (shouldAddLastWeek) {\n                // Add last empty week item if the other month has an extra week\n                for (let i = 0; i < dayItems.length; i++) {\n                    dayItems[i].weeks.push(lastWeeks[i]);\n                }\n            }\n\n            return dayItems;\n        },\n    })\n    .set(\"months\", {\n        mainTitle: _t(\"Select year\"),\n        nextTitle: _t(\"Next year\"),\n        prevTitle: _t(\"Previous year\"),\n        step: { year: 1 },\n        getTitle: (date) => String(date.year),\n        getItems: (date, { maxDate, minDate }) => {\n            const startOfYear = date.startOf(\"year\");\n            return numberRange(0, 12).map((i) => {\n                const startOfMonth = startOfYear.plus({ month: i });\n                const range = [startOfMonth, startOfMonth.endOf(\"month\")];\n                return toDateItem({\n                    isValid: isInRange(range, [minDate, maxDate]),\n                    label: \"monthShort\",\n                    range,\n                });\n            });\n        },\n    })\n    .set(\"years\", {\n        mainTitle: _t(\"Select decade\"),\n        nextTitle: _t(\"Next decade\"),\n        prevTitle: _t(\"Previous decade\"),\n        step: { year: 10 },\n        getTitle: (date) => `${getStartOfDecade(date) - 1} - ${getStartOfDecade(date) + 10}`,\n        getItems: (date, { maxDate, minDate }) => {\n            const startOfDecade = date.startOf(\"year\").set({ year: getStartOfDecade(date) });\n            return numberRange(-GRID_MARGIN, GRID_COUNT + GRID_MARGIN).map((i) => {\n                const startOfYear = startOfDecade.plus({ year: i });\n                const range = [startOfYear, startOfYear.endOf(\"year\")];\n                return toDateItem({\n                    isOutOfRange: i < 0 || i >= GRID_COUNT,\n                    isValid: isInRange(range, [minDate, maxDate]),\n                    label: \"year\",\n                    range,\n                });\n            });\n        },\n    })\n    .set(\"decades\", {\n        mainTitle: _t(\"Select century\"),\n        nextTitle: _t(\"Next century\"),\n        prevTitle: _t(\"Previous century\"),\n        step: { year: 100 },\n        getTitle: (date) => `${getStartOfCentury(date) - 10} - ${getStartOfCentury(date) + 100}`,\n        getItems: (date, { maxDate, minDate }) => {\n            const startOfCentury = date.startOf(\"year\").set({ year: getStartOfCentury(date) });\n            return numberRange(-GRID_MARGIN, GRID_COUNT + GRID_MARGIN).map((i) => {\n                const startOfDecade = startOfCentury.plus({ year: i * 10 });\n                const range = [startOfDecade, startOfDecade.plus({ year: 10, millisecond: -1 })];\n                return toDateItem({\n                    label: \"year\",\n                    isOutOfRange: i < 0 || i >= GRID_COUNT,\n                    isValid: isInRange(range, [minDate, maxDate]),\n                    range,\n                });\n            });\n        },\n    });\n\n// Other constants\nconst GRID_COUNT = 10;\nconst GRID_MARGIN = 1;\nconst NULLABLE_DATETIME_PROPERTY = [DateTime, { value: false }, { value: null }];\n\nconst DAYS_PER_WEEK = 7;\nconst WEEKS_PER_MONTH = 6;\n\n/** @extends {Component<DateTimePickerProps>} */\nexport class DateTimePicker extends Component {\n    static props = {\n        focusedDateIndex: { type: Number, optional: true },\n        showWeekNumbers: { type: Boolean, optional: true },\n        daysOfWeekFormat: { type: String, optional: true },\n        maxDate: { type: [NULLABLE_DATETIME_PROPERTY, { value: \"today\" }], optional: true },\n        maxPrecision: {\n            type: [...PRECISION_LEVELS.keys()].map((value) => ({ value })),\n            optional: true,\n        },\n        minDate: { type: [NULLABLE_DATETIME_PROPERTY, { value: \"today\" }], optional: true },\n        minPrecision: {\n            type: [...PRECISION_LEVELS.keys()].map((value) => ({ value })),\n            optional: true,\n        },\n        onReset: { type: Function, optional: true },\n        onSelect: { type: Function, optional: true },\n        onToggleRange: { type: Function, optional: true },\n        range: { type: Boolean, optional: true },\n        rounding: { type: Number, optional: true },\n        showRangeToggler: { type: Boolean, optional: true },\n        slots: {\n            type: Object,\n            shape: { buttons: { type: Object, optional: true } },\n            optional: true,\n        },\n        type: { type: [{ value: \"date\" }, { value: \"datetime\" }], optional: true },\n        value: {\n            type: [\n                NULLABLE_DATETIME_PROPERTY,\n                { type: Array, element: NULLABLE_DATETIME_PROPERTY },\n            ],\n            optional: true,\n        },\n        isDateValid: { type: Function, optional: true },\n        dayCellClass: { type: Function, optional: true },\n        tz: { type: String, optional: true },\n    };\n\n    static defaultProps = {\n        focusedDateIndex: 0,\n        daysOfWeekFormat: \"narrow\",\n        maxPrecision: \"decades\",\n        minPrecision: \"days\",\n        rounding: 5,\n        showWeekNumbers: true,\n        type: \"datetime\",\n    };\n\n    static template = \"web.DateTimePicker\";\n    static components = { TimePicker };\n\n    //-------------------------------------------------------------------------\n    // Getters\n    //-------------------------------------------------------------------------\n\n    get activePrecisionLevel() {\n        return PRECISION_LEVELS.get(this.state.precision);\n    }\n\n    get isLastPrecisionLevel() {\n        return (\n            this.allowedPrecisionLevels.indexOf(this.state.precision) ===\n            this.allowedPrecisionLevels.length - 1\n        );\n    }\n\n    get titles() {\n        return ensureArray(this.title);\n    }\n\n    //-------------------------------------------------------------------------\n    // Lifecycle\n    //-------------------------------------------------------------------------\n\n    setup() {\n        /** @type {PrecisionLevel[]} */\n        this.allowedPrecisionLevels = [];\n        /** @type {Item[]} */\n        this.items = [];\n        this.title = \"\";\n        this.shouldAdjustFocusDate = false;\n\n        this.state = useState({\n            /** @type {DateTime | null} */\n            focusDate: null,\n            /** @type {DateTime | null} */\n            hoveredDate: null,\n            /** @type {Time[]} */\n            timeValues: [],\n            /** @type {PrecisionLevel} */\n            precision: this.props.minPrecision,\n        });\n\n        this.onPropsUpdated(this.props);\n        onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps));\n\n        onWillRender(() => this.onWillRender());\n    }\n\n    /**\n     * @param {DateTimePickerProps} props\n     */\n    onPropsUpdated(props) {\n        /** @type {[NullableDateTime] | NullableDateRange} */\n        this.values = ensureArray(props.value).map((value) =>\n            value && !value.isValid ? null : value\n        );\n        this.allowedPrecisionLevels = this.filterPrecisionLevels(\n            props.minPrecision,\n            props.maxPrecision\n        );\n\n        this.maxDate = parseLimitDate(props.maxDate, MAX_VALID_DATE);\n        this.minDate = parseLimitDate(props.minDate, MIN_VALID_DATE);\n        if (this.props.type === \"date\") {\n            this.maxDate = this.maxDate.endOf(\"day\");\n            this.minDate = this.minDate.startOf(\"day\");\n        }\n\n        if (this.maxDate < this.minDate) {\n            throw new Error(`DateTimePicker error: given \"maxDate\" comes before \"minDate\".`);\n        }\n\n        this.state.timeValues = this.getTimeValues(props);\n        this.shouldAdjustFocusDate = !props.range;\n        this.adjustFocus(this.values, props.focusedDateIndex);\n    }\n\n    onWillRender() {\n        const { dayCellClass, focusedDateIndex, isDateValid, range, showWeekNumbers } = this.props;\n        const { focusDate, hoveredDate } = this.state;\n        const precision = this.activePrecisionLevel;\n        const getterParams = {\n            maxDate: this.maxDate,\n            minDate: this.minDate,\n            showWeekNumbers: showWeekNumbers ?? !range,\n            isDateValid,\n            dayCellClass,\n        };\n\n        this.title = precision.getTitle(focusDate);\n        this.items = precision.getItems(focusDate, getterParams);\n\n        this.selectedRange = [...this.values];\n        if (range && focusedDateIndex > 0 && (!this.values[1] || hoveredDate > this.values[0])) {\n            this.selectedRange[1] = hoveredDate;\n        }\n    }\n\n    //-------------------------------------------------------------------------\n    // Methods\n    //-------------------------------------------------------------------------\n\n    /**\n     * @param {NullableDateTime[]} values\n     * @param {number} focusedDateIndex\n     */\n    adjustFocus(values, focusedDateIndex) {\n        if (!this.shouldAdjustFocusDate && this.state.focusDate) {\n            return;\n        }\n\n        const dateToFocus =\n            values[focusedDateIndex] || values[focusedDateIndex === 1 ? 0 : 1] || today();\n\n        this.shouldAdjustFocusDate = false;\n        this.state.focusDate = this.clamp(dateToFocus.startOf(\"month\"));\n    }\n\n    /**\n     * @param {DateTime} value\n     */\n    clamp(value) {\n        return clampDate(value, this.minDate, this.maxDate);\n    }\n\n    /**\n     * @param {PrecisionLevel} minPrecision\n     * @param {PrecisionLevel} maxPrecision\n     */\n    filterPrecisionLevels(minPrecision, maxPrecision) {\n        const levels = [...PRECISION_LEVELS.keys()];\n        return levels.slice(levels.indexOf(minPrecision), levels.indexOf(maxPrecision) + 1);\n    }\n\n    /**\n     * Returns various flags indicating what ranges the current date item belongs\n     * to. Note that these ranges are computed differently according to the current\n     * value mode (range or single date). This is done to simplify CSS selectors.\n     * - Selected Range:\n     *      > range: current values with hovered date applied\n     *      > single date: just the hovered date\n     * - Highlighted Range:\n     *      > range: union of selection range and current values\n     *      > single date: just the current value\n     * - Current Range (range only):\n     *      > range: current start date or current end date.\n     * @param {DateItem} item\n     */\n    getActiveRangeInfo({ range }) {\n        const result = {\n            isSelected: isInRange(this.selectedRange, range),\n            isSelectStart: false,\n            isSelectEnd: false,\n            isHighlighted: isInRange(this.state.hoveredDate, range),\n        };\n\n        if (this.props.range) {\n            if (result.isSelected) {\n                const [selectStart, selectEnd] = this.selectedRange.sort();\n                result.isSelectStart = !selectStart || isInRange(selectStart, range);\n                result.isSelectEnd = !selectEnd || isInRange(selectEnd, range);\n            }\n        } else {\n            result.isSelectStart = result.isSelectEnd = result.isSelected;\n        }\n\n        return result;\n    }\n\n    /**\n     * @param {DateTimePickerProps} props\n     */\n    getTimeValues(props) {\n        const timeValues = this.values.map(\n            (val, index) =>\n                new Time({\n                    hour:\n                        index === 1 && !this.values[1]\n                            ? (val || DateTime.local()).hour + 1\n                            : (val || DateTime.local()).hour,\n                    minute: val?.minute || 0,\n                    second: val?.second || 0,\n                })\n        );\n\n        if (props.range) {\n            return timeValues;\n        } else {\n            const values = [];\n            values[props.focusedDateIndex] = timeValues[props.focusedDateIndex];\n            return values;\n        }\n    }\n\n    /**\n     * @param {DateItem} item\n     */\n    isSelectedDate({ range }) {\n        return this.values.some((value) => isInRange(value, range));\n    }\n\n    /**\n     * Goes to the next panel (e.g. next month if precision is \"days\").\n     * If an event is given it will be prevented.\n     * @param {PointerEvent} ev\n     */\n    next(ev) {\n        ev.preventDefault();\n        const { step } = this.activePrecisionLevel;\n        this.state.focusDate = this.clamp(this.state.focusDate.plus(step));\n    }\n\n    /**\n     * Goes to the previous panel (e.g. previous month if precision is \"days\").\n     * If an event is given it will be prevented.\n     * @param {PointerEvent} ev\n     */\n    previous(ev) {\n        ev.preventDefault();\n        const { step } = this.activePrecisionLevel;\n        this.state.focusDate = this.clamp(this.state.focusDate.minus(step));\n    }\n\n    /**\n     * @param {number} valueIndex\n     * @param {Time} newTime\n     */\n    onTimeChange(valueIndex, newTime) {\n        this.state.timeValues[valueIndex] = newTime;\n        const value = this.values[valueIndex] || today();\n        this.validateAndSelect(value, valueIndex, \"time\");\n    }\n\n    /**\n     * @param {DateTime} value\n     * @param {number} valueIndex\n     * @param {\"date\" | \"time\"} unit\n     */\n    validateAndSelect(value, valueIndex, unit) {\n        if (!this.props.onSelect) {\n            // No onSelect handler\n            return false;\n        }\n\n        const result = [...this.values];\n        result[valueIndex] = value;\n\n        if (this.props.type === \"datetime\") {\n            // Adjusts result according to the current time values\n            const { hour, minute, second } = this.state.timeValues[valueIndex];\n            result[valueIndex] = result[valueIndex].set({ hour, minute, second });\n        }\n        if (!isInRange(result[valueIndex], [this.minDate, this.maxDate])) {\n            // Date is outside range defined by min and max dates\n            return false;\n        }\n        this.props.onSelect(result.length === 2 ? result : result[0], unit);\n        return true;\n    }\n\n    /**\n     * Returns whether the zoom has occurred\n     * @param {DateTime} date\n     */\n    zoomIn(date) {\n        const index = this.allowedPrecisionLevels.indexOf(this.state.precision) - 1;\n        if (index in this.allowedPrecisionLevels) {\n            this.state.focusDate = this.clamp(date);\n            this.state.precision = this.allowedPrecisionLevels[index];\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Returns whether the zoom has occurred\n     */\n    zoomOut() {\n        const index = this.allowedPrecisionLevels.indexOf(this.state.precision) + 1;\n        if (index in this.allowedPrecisionLevels) {\n            this.state.precision = this.allowedPrecisionLevels[index];\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Happens when a date item is selected:\n     * - first tries to zoom in on the item\n     * - if could not zoom in: date is considered as final value and triggers a hard select\n     * @param {DateItem} dateItem\n     */\n    zoomOrSelect(dateItem) {\n        if (!dateItem.isValid) {\n            // Invalid item\n            return;\n        }\n        if (this.zoomIn(dateItem.range[0])) {\n            // Zoom was successful\n            return;\n        }\n        const [value] = dateItem.range;\n        const valueIndex = this.props.focusedDateIndex;\n        const isValid = this.validateAndSelect(value, valueIndex, \"date\");\n        this.shouldAdjustFocusDate = isValid && !this.props.range;\n    }\n}\n", "import { onWillDestroy, useRef } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\n\n/**\n * @typedef {import(\"./datetimepicker_service\").DateTimePickerServiceParams & {\n *  endDateRefName?: string;\n *  startDateRefName?: string;\n * }} DateTimePickerHookParams\n */\n\n/**\n * @param {DateTimePickerHookParams} params\n */\nexport function useDateTimePicker(params) {\n    function getInputs() {\n        return inputRefs.map((ref) => ref.el);\n    }\n\n    const inputRefs = [\n        useRef(params.startDateRefName || \"start-date\"),\n        useRef(params.endDateRefName || \"end-date\"),\n    ];\n\n    // Need original object since 'pickerProps' (or any other param) can be defined\n    // as getters\n    const serviceParams = Object.assign(Object.create(params), {\n        getInputs,\n        useOwlHooks: true,\n    });\n\n    const picker = useService(\"datetime_picker\").create(serviceParams);\n    onWillDestroy(() => {\n        picker.disable();\n    });\n    return picker;\n}\n", "import { Component } from \"@odoo/owl\";\nimport { useHotkey } from \"../hotkeys/hotkey_hook\";\nimport { DateTimePicker } from \"./datetime_picker\";\n\n/**\n * @typedef {import(\"./datetime_picker\").DateTimePickerProps} DateTimePickerProps\n *\n * @typedef DateTimePickerPopoverProps\n * @property {() => void} close\n * @property {DateTimePickerProps} pickerProps\n */\n\n/** @extends {Component<DateTimePickerPopoverProps>} */\nexport class DateTimePickerPopover extends Component {\n    static components = { DateTimePicker };\n\n    static props = {\n        close: Function, // Given by the Popover service\n        pickerProps: { type: Object, shape: DateTimePicker.props },\n    };\n\n    static template = \"web.DateTimePickerPopover\";\n\n    //-------------------------------------------------------------------------\n    // Lifecycle\n    //-------------------------------------------------------------------------\n\n    setup() {\n        useHotkey(\"enter\", () => this.props.close());\n    }\n}\n", "import { markRaw, onPatched, onWillRender, reactive, useEffect, useRef } from \"@odoo/owl\";\nimport { areDatesEqual, formatDate, formatDateTime, parseDate, parseDateTime } from \"../l10n/dates\";\nimport { makePopover } from \"../popover/popover_hook\";\nimport { registry } from \"../registry\";\nimport { ensureArray, zip, zipWith } from \"../utils/arrays\";\nimport { shallowEqual } from \"../utils/objects\";\nimport { DateTimePicker } from \"./datetime_picker\";\nimport { DateTimePickerPopover } from \"./datetime_picker_popover\";\n\n/**\n * @typedef {luxon[\"DateTime\"][\"prototype\"]} DateTime\n *\n * @typedef {import(\"./datetime_picker\").DateTimePickerProps} DateTimePickerProps\n * @typedef {import(\"../popover/popover_hook\").PopoverHookReturnType} PopoverHookReturnType\n * @typedef {import(\"../popover/popover_service\").PopoverServiceAddOptions} PopoverServiceAddOptions\n * @typedef {import(\"@odoo/owl\").Component} Component\n * @typedef {ReturnType<typeof import(\"@odoo/owl\").useRef>} OwlRef\n *\n * @typedef {{\n *  createPopover?: (component: Component, options: PopoverServiceAddOptions) => PopoverHookReturnType;\n *  ensureVisibility?: () => boolean;\n *  format?: string;\n *  getInputs?: () => HTMLElement[];\n *  onApply?: (value: DateTimePickerProps[\"value\"]) => any;\n *  onChange?: (value: DateTimePickerProps[\"value\"]) => any;\n *  onClose?: () => any;\n *  pickerProps?: DateTimePickerProps;\n *  showSeconds?: boolean;\n *  target: HTMLElement | string;\n *  useOwlHooks?: boolean;\n * }} DateTimePickerServiceParams\n */\n\n/**\n * @template {object} T\n * @param {T} obj\n */\nfunction markValuesRaw(obj) {\n    /** @type {T} */\n    const copy = {};\n    for (const [key, value] of Object.entries(obj)) {\n        if (value && typeof value === \"object\") {\n            copy[key] = markRaw(value);\n        } else {\n            copy[key] = value;\n        }\n    }\n    return copy;\n}\n\n/**\n * @param {Record<string, any>} props\n */\nfunction stringifyProps(props) {\n    const copy = {};\n    for (const [key, value] of Object.entries(props)) {\n        copy[key] = JSON.stringify(value);\n    }\n    return copy;\n}\n\nconst FOCUS_CLASSNAME = \"text-primary\";\n\nconst formatters = {\n    date: formatDate,\n    datetime: formatDateTime,\n};\nconst listenedElements = new WeakSet();\nconst parsers = {\n    date: parseDate,\n    datetime: parseDateTime,\n};\n\nexport const datetimePickerService = {\n    dependencies: [\"popover\"],\n    start(env, { popover: popoverService }) {\n        const dateTimePickerList = new Set();\n        return {\n            /**\n             * @param {DateTimePickerServiceParams} [params]\n             */\n            create(params = {}) {\n                /**\n                 * Wrapper method on the \"onApply\" callback to only call it when the\n                 * value has changed, and set other internal variables accordingly.\n                 */\n                async function apply() {\n                    const { value } = pickerProps;\n                    const stringValue = JSON.stringify(value);\n                    if (\n                        stringValue === lastAppliedStringValue ||\n                        stringValue === stringProps.value\n                    ) {\n                        return;\n                    }\n\n                    lastAppliedStringValue = stringValue;\n                    inputsChanged = ensureArray(value).map(() => false);\n\n                    await params.onApply?.(value);\n\n                    stringProps.value = stringValue;\n                }\n\n                function enable() {\n                    for (const [el, value] of zip(\n                        getInputs(),\n                        ensureArray(pickerProps.value),\n                        true\n                    )) {\n                        updateInput(el, value);\n                        if (el && !el.disabled && !el.readOnly && !listenedElements.has(el)) {\n                            listenedElements.add(el);\n                            el.addEventListener(\"change\", onInputChange);\n                            el.addEventListener(\"click\", onInputClick);\n                            el.addEventListener(\"focus\", onInputFocus);\n                            el.addEventListener(\"keydown\", onInputKeydown);\n                        }\n                    }\n                    const calendarIconGroupEl = getInput(0)?.parentElement.querySelector(\n                        \".o_input_group_date_icon\"\n                    );\n                    if (calendarIconGroupEl) {\n                        calendarIconGroupEl.classList.add(\"cursor-pointer\");\n                        calendarIconGroupEl.addEventListener(\"click\", () => open(0));\n                    }\n                    return () => {};\n                }\n\n                /**\n                 * Ensures the current focused input (indicated by `pickerProps.focusedDateIndex`)\n                 * is actually focused.\n                 */\n                function focusActiveInput() {\n                    const inputEl = getInput(pickerProps.focusedDateIndex);\n                    if (!inputEl) {\n                        shouldFocus = true;\n                        return;\n                    }\n\n                    const { activeElement } = inputEl.ownerDocument;\n                    if (activeElement !== inputEl) {\n                        inputEl.focus();\n                    }\n                    setInputFocus(inputEl);\n                }\n\n                /**\n                 * @param {number} valueIndex\n                 * @returns {HTMLInputElement | null}\n                 */\n                function getInput(valueIndex) {\n                    const el = getInputs()[valueIndex];\n                    if (el?.isConnected) {\n                        return el;\n                    }\n                    return null;\n                }\n\n                /**\n                 * Returns the appropriate root element to attach the popover:\n                 * - if the value is a range: the closest common parent of the two inputs\n                 * - if not: the first input\n                 */\n                function getPopoverTarget() {\n                    const target = getTarget();\n                    if (target) {\n                        return target;\n                    }\n                    if (pickerProps.range) {\n                        let parentElement = getInput(0).parentElement;\n                        const inputEls = getInputs();\n                        while (\n                            parentElement &&\n                            !inputEls.every((inputEl) => parentElement.contains(inputEl))\n                        ) {\n                            parentElement = parentElement.parentElement;\n                        }\n                        return parentElement || getInput(0);\n                    } else {\n                        return getInput(0);\n                    }\n                }\n\n                function getTarget() {\n                    return targetRef ? targetRef.el : params.target;\n                }\n\n                function isOpen() {\n                    return popover.isOpen;\n                }\n\n                /**\n                 * Inputs \"change\" event handler. This will trigger an \"onApply\" callback if\n                 * one of the following is true:\n                 * - there is only one input;\n                 * - the popover is closed;\n                 * - the other input has also changed.\n                 *\n                 * @param {Event} ev\n                 */\n                function onInputChange(ev) {\n                    updateValueFromInputs();\n                    inputsChanged[ev.target === getInput(1) ? 1 : 0] = true;\n                    if (!isOpen() || inputsChanged.every(Boolean)) {\n                        saveAndClose();\n                    }\n                }\n\n                /**\n                 * @param {PointerEvent} ev\n                 */\n                function onInputClick({ target }) {\n                    open(target === getInput(1) ? 1 : 0);\n                }\n\n                /**\n                 * @param {FocusEvent} ev\n                 */\n                function onInputFocus({ target }) {\n                    pickerProps.focusedDateIndex = target === getInput(1) ? 1 : 0;\n                    setInputFocus(target);\n                }\n\n                /**\n                 * @param {KeyboardEvent} ev\n                 */\n                function onInputKeydown(ev) {\n                    if (ev.key == \"Enter\" && ev.ctrlKey) {\n                        ev.preventDefault();\n                        updateValueFromInputs();\n                        return open(ev.target === getInput(1) ? 1 : 0);\n                    }\n                    switch (ev.key) {\n                        case \"Enter\":\n                        case \"Escape\": {\n                            return saveAndClose();\n                        }\n                        case \"Tab\": {\n                            if (\n                                !getInput(0) ||\n                                !getInput(1) ||\n                                ev.target !== getInput(ev.shiftKey ? 1 : 0)\n                            ) {\n                                return saveAndClose();\n                            }\n                        }\n                    }\n                }\n\n                /**\n                 * @param {number} inputIndex Input from which to open the picker\n                 */\n                function open(inputIndex) {\n                    pickerProps.focusedDateIndex = inputIndex;\n\n                    if (!isOpen()) {\n                        const popoverTarget = getPopoverTarget();\n                        if (ensureVisibility()) {\n                            const { marginBottom } = popoverTarget.style;\n                            // Adds enough space for the popover to be displayed below the target\n                            // even on small screens.\n                            popoverTarget.style.marginBottom = `100vh`;\n                            popoverTarget.scrollIntoView(true);\n                            restoreTargetMargin = async () => {\n                                popoverTarget.style.marginBottom = marginBottom;\n                            };\n                        }\n                        for (const picker of dateTimePickerList) {\n                            picker.close();\n                        }\n                        popover.open(popoverTarget, { pickerProps });\n                    }\n\n                    focusActiveInput();\n                }\n\n                /**\n                 * @template {\"format\" | \"parse\"} T\n                 * @param {T} operation\n                 * @param {T extends \"format\" ? DateTime : string} value\n                 * @returns {[T extends \"format\" ? string : DateTime, null] | [null, Error]}\n                 */\n                function safeConvert(operation, value) {\n                    const { type } = pickerProps;\n                    const convertFn = (operation === \"format\" ? formatters : parsers)[type];\n                    const options = { tz: pickerProps.tz, format: params.format };\n                    if (operation === \"format\") {\n                        options.showSeconds = params.showSeconds ?? true;\n                    }\n                    try {\n                        return [convertFn(value, options), null];\n                    } catch (error) {\n                        if (error?.name === \"ConversionError\") {\n                            return [null, error];\n                        } else {\n                            throw error;\n                        }\n                    }\n                }\n\n                /**\n                 * Wrapper method to ensure the \"onApply\" callback is called, either:\n                 * - by closing the popover (if any);\n                 * - or by directly calling \"apply\", without updating the values.\n                 */\n                function saveAndClose() {\n                    if (isOpen()) {\n                        // apply will be done in the \"onClose\" callback\n                        popover.close();\n                    } else {\n                        apply();\n                    }\n                }\n\n                /**\n                 * Updates class names on given inputs according to the currently selected input.\n                 *\n                 * @param {HTMLInputElement | null} input\n                 */\n                function setFocusClass(input) {\n                    for (const el of getInputs()) {\n                        if (el) {\n                            el.classList.toggle(FOCUS_CLASSNAME, isOpen() && el === input);\n                        }\n                    }\n                }\n\n                /**\n                 * Applies class names to all inputs according to whether they are focused or not.\n                 *\n                 * @param {HTMLInputElement} inputEl\n                 */\n                function setInputFocus(inputEl) {\n                    inputEl.selectionStart = 0;\n                    inputEl.selectionEnd = inputEl.value.length;\n\n                    setFocusClass(inputEl);\n\n                    shouldFocus = false;\n                }\n\n                /**\n                 * Synchronizes the given input with the given value.\n                 *\n                 * @param {HTMLInputElement} el\n                 * @param {DateTime} value\n                 */\n                function updateInput(el, value) {\n                    if (!el) {\n                        return;\n                    }\n                    const [formattedValue] = safeConvert(\"format\", value);\n                    el.value = formattedValue || \"\";\n                }\n\n                /**\n                 * @param {DateTimePickerProps[\"value\"]} value\n                 * @param {\"date\" | \"time\"} unit\n                 * @param {\"input\" | \"picker\"} source\n                 */\n                function updateValue(value, unit, source) {\n                    if (source === \"input\" && areDatesEqual(pickerProps.value, value)) {\n                        return;\n                    }\n\n                    pickerProps.value = value;\n\n                    if (pickerProps.range && unit !== \"time\" && source === \"picker\") {\n                        if (!value[0]) {\n                            pickerProps.focusedDateIndex = 0;\n                        } else if (\n                            pickerProps.focusedDateIndex === 0 ||\n                            (value[0] && value[1] && value[1] < value[0])\n                        ) {\n                            // If selecting either:\n                            // - the first value\n                            // - OR a second value before the first:\n                            // Then:\n                            // - Set the DATE (year + month + day) of all values\n                            // to the one that has been selected.\n                            const { year, month, day } = value[pickerProps.focusedDateIndex];\n                            for (let i = 0; i < value.length; i++) {\n                                value[i] = value[i] && value[i].set({ year, month, day });\n                            }\n                            pickerProps.focusedDateIndex = 1;\n                        } else {\n                            // If selecting the second value after the first:\n                            // - simply toggle the focus index\n                            pickerProps.focusedDateIndex =\n                                pickerProps.focusedDateIndex === 1 ? 0 : 1;\n                        }\n                    }\n\n                    params.onChange?.(value);\n                }\n\n                function updateValueFromInputs() {\n                    const values = zipWith(\n                        getInputs(),\n                        ensureArray(pickerProps.value),\n                        (el, currentValue) => {\n                            if (!el || el.tagName?.toLowerCase() !== \"input\") {\n                                return currentValue;\n                            }\n                            const [parsedValue, error] = safeConvert(\"parse\", el.value);\n                            if (error) {\n                                updateInput(el, currentValue);\n                                return currentValue;\n                            } else {\n                                return parsedValue;\n                            }\n                        }\n                    );\n                    updateValue(values.length === 2 ? values : values[0], \"date\", \"input\");\n                }\n\n                const createPopover =\n                    params.createPopover ||\n                    function defaultCreatePopover(...args) {\n                        return makePopover(popoverService.add, ...args);\n                    };\n                const ensureVisibility =\n                    params.ensureVisibility ||\n                    function defaultEnsureVisibility() {\n                        return env.isSmall;\n                    };\n                const getInputs =\n                    params.getInputs ||\n                    function defaultGetInputs() {\n                        return [getTarget(), null];\n                    };\n\n                // Hook variables\n\n                /** @type {DateTimePickerProps} */\n                const rawPickerProps = {\n                    ...DateTimePicker.defaultProps,\n                    onReset: () => {\n                        updateValue(\n                            ensureArray(pickerProps.value).length === 2 ? [false, false] : false,\n                            \"date\",\n                            \"picker\"\n                        );\n                        saveAndClose();\n                    },\n                    onSelect: (value, unit) => {\n                        value &&= markRaw(value);\n                        updateValue(value, unit, \"picker\");\n                        if (!pickerProps.range && pickerProps.type === \"date\") {\n                            saveAndClose();\n                        }\n                    },\n                    ...markValuesRaw(params.pickerProps),\n                };\n                const pickerProps = reactive(rawPickerProps, () => {\n                    // Update inputs\n                    for (const [el, value] of zip(\n                        getInputs(),\n                        ensureArray(pickerProps.value),\n                        true\n                    )) {\n                        if (el) {\n                            updateInput(el, value);\n                            // Apply changes immediately if the popover is already closed.\n                            // Otherwise \u00b4apply()\u00b4 will be called later on close.\n                            if (!isOpen()) {\n                                apply();\n                            }\n                        }\n                    }\n\n                    shouldFocus = true;\n                });\n                const popover = createPopover(DateTimePickerPopover, {\n                    async onClose() {\n                        updateValueFromInputs();\n                        setFocusClass(null);\n                        restoreTargetMargin?.();\n                        restoreTargetMargin = null;\n                        await apply();\n                        params.onClose?.();\n                    },\n                });\n\n                /** @type {boolean[]} */\n                let inputsChanged = [];\n                let lastAppliedStringValue = \"\";\n                /** @type {(() => void) | null} */\n                let restoreTargetMargin = null;\n                let shouldFocus = false;\n                /** @type {Partial<DateTimePickerProps>} */\n                let stringProps = {};\n                /** @type {OwlRef | null} */\n                let targetRef = null;\n\n                if (params.useOwlHooks) {\n                    if (typeof params.target === \"string\") {\n                        targetRef = useRef(params.target);\n                    }\n\n                    onWillRender(function computeBasePickerProps() {\n                        const nextProps = markValuesRaw(params.pickerProps);\n                        const oldStringProps = stringProps;\n\n                        stringProps = stringifyProps(nextProps);\n                        lastAppliedStringValue = stringProps.value;\n\n                        if (shallowEqual(oldStringProps, stringProps)) {\n                            return;\n                        }\n\n                        inputsChanged = ensureArray(nextProps.value).map(() => false);\n\n                        for (const [key, value] of Object.entries(nextProps)) {\n                            if (!areDatesEqual(pickerProps[key], value)) {\n                                pickerProps[key] = value;\n                            }\n                        }\n                    });\n\n                    useEffect(enable, getInputs);\n\n                    // Note: this `onPatched` callback must be called after the `useEffect` since\n                    // the effect may change input values that will be selected by the patch callback.\n                    onPatched(function focusIfNeeded() {\n                        if (isOpen() && shouldFocus) {\n                            focusActiveInput();\n                        }\n                    });\n                } else if (typeof params.target === \"string\") {\n                    throw new Error(\n                        `datetime picker service error: cannot use target as ref name when not using Owl hooks`\n                    );\n                }\n                const picker = {\n                    enable,\n                    disable: () => dateTimePickerList.delete(picker),\n                    isOpen,\n                    open,\n                    close: () => popover.close(),\n                    state: pickerProps,\n                };\n                dateTimePickerList.add(picker);\n                return picker;\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"datetime_picker\", datetimePickerService);\n", "import { user } from \"@web/core/user\";\nimport { registry } from \"../registry\";\n\nimport { useEffect, useEnv, useSubEnv } from \"@odoo/owl\";\nconst debugRegistry = registry.category(\"debug\");\n\nconst getAccessRights = async () => {\n    const rightsToCheck = {\n        \"ir.ui.view\": \"write\",\n        \"ir.rule\": \"read\",\n        \"ir.model.access\": \"read\",\n    };\n    const proms = Object.entries(rightsToCheck).map(([model, operation]) => {\n        return user.checkAccessRight(model, operation);\n    });\n    const [canEditView, canSeeRecordRules, canSeeModelAccess] = await Promise.all(proms);\n    const accessRights = { canEditView, canSeeRecordRules, canSeeModelAccess };\n    return accessRights;\n};\n\nclass DebugContext {\n    constructor(defaultCategories) {\n        this.categories = new Map(defaultCategories.map((cat) => [cat, [{}]]));\n    }\n\n    activateCategory(category, context) {\n        const contexts = this.categories.get(category) || new Set();\n        contexts.add(context);\n        this.categories.set(category, contexts);\n\n        return () => {\n            contexts.delete(context);\n            if (contexts.size === 0) {\n                this.categories.delete(category);\n            }\n        };\n    }\n\n    async getItems(env) {\n        const accessRights = await getAccessRights();\n        return [...this.categories.entries()]\n            .flatMap(([category, contexts]) => {\n                return debugRegistry\n                    .category(category)\n                    .getAll()\n                    .map((factory) => factory(Object.assign({ env, accessRights }, ...contexts)));\n            })\n            .filter(Boolean)\n            .sort((x, y) => {\n                const xSeq = x.sequence || 1000;\n                const ySeq = y.sequence || 1000;\n                return xSeq - ySeq;\n            });\n    }\n}\n\nconst debugContextSymbol = Symbol(\"debugContext\");\nexport function createDebugContext({ categories = [] } = {}) {\n    return { [debugContextSymbol]: new DebugContext(categories) };\n}\n\nexport function useOwnDebugContext({ categories = [] } = {}) {\n    useSubEnv(createDebugContext({ categories }));\n}\n\nexport function useEnvDebugContext() {\n    const debugContext = useEnv()[debugContextSymbol];\n    if (!debugContext) {\n        throw new Error(\"There is no debug context available in the current environment.\");\n    }\n    return debugContext;\n}\n\nexport function useDebugCategory(category, context = {}) {\n    const env = useEnv();\n    if (env.debug) {\n        const debugContext = useEnvDebugContext();\n        useEffect(\n            () => debugContext.activateCategory(category, context),\n            () => []\n        );\n    }\n}\n", "import { useEnvDebugContext } from \"./debug_context\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { groupBy, sortBy } from \"@web/core/utils/arrays\";\n\nimport { Component } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\n\nconst debugSectionRegistry = registry.category(\"debug_section\");\n\ndebugSectionRegistry\n    .add(\"record\", { label: _t(\"Record\"), sequence: 10 })\n    .add(\"records\", { label: _t(\"Records\"), sequence: 10 })\n    .add(\"ui\", { label: _t(\"User Interface\"), sequence: 20 })\n    .add(\"security\", { label: _t(\"Security\"), sequence: 30 })\n    .add(\"testing\", { label: _t(\"Tours & Testing\"), sequence: 40 })\n    .add(\"tools\", { label: _t(\"Tools\"), sequence: 50 });\n\nexport class DebugMenuBasic extends Component {\n    static template = \"web.DebugMenu\";\n    static components = {\n        Dropdown,\n        DropdownItem,\n    };\n    static props = {};\n\n    setup() {\n        this.debugContext = useEnvDebugContext();\n    }\n\n    async loadGroupedItems() {\n        const items = await this.debugContext.getItems(this.env);\n        const sections = groupBy(items, (item) => item.section || \"\");\n        this.sectionEntries = sortBy(\n            Object.entries(sections),\n            ([section]) => debugSectionRegistry.get(section, { sequence: 50 }).sequence\n        );\n    }\n\n    getSectionLabel(section) {\n        return debugSectionRegistry.get(section, { label: section }).label;\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { router } from \"@web/core/browser/router\";\nimport { registry } from \"@web/core/registry\";\nimport { user } from \"@web/core/user\";\n\nfunction activateTestsAssetsDebugging({ env }) {\n    if (String(router.current.debug).includes(\"tests\")) {\n        return;\n    }\n\n    return {\n        type: \"item\",\n        description: _t(\"Activate Test Mode\"),\n        callback: () => {\n            router.pushState({ debug: \"assets,tests\" }, { reload: true });\n        },\n        sequence: 580,\n        section: \"tools\",\n    };\n}\n\nexport function regenerateAssets({ env }) {\n    return {\n        type: \"item\",\n        description: _t(\"Regenerate Assets\"),\n        callback: async () => {\n            await env.services.orm.call(\"ir.attachment\", \"regenerate_assets_bundles\");\n            browser.location.reload();\n        },\n        sequence: 550,\n        section: \"tools\",\n    };\n}\n\nexport function becomeSuperuser({ env }) {\n    const becomeSuperuserURL = browser.location.origin + \"/web/become\";\n    if (!user.isAdmin) {\n        return false;\n    }\n    return {\n        type: \"item\",\n        description: _t(\"Become Superuser\"),\n        href: becomeSuperuserURL,\n        callback: () => {\n            browser.open(becomeSuperuserURL, \"_self\");\n        },\n        sequence: 560,\n        section: \"tools\",\n    };\n}\n\nfunction leaveDebugMode() {\n    return {\n        type: \"item\",\n        description: _t(\"Leave Debug Mode\"),\n        callback: () => {\n            router.pushState({ debug: 0 }, { reload: true });\n        },\n        sequence: 650,\n    };\n}\n\nregistry\n    .category(\"debug\")\n    .category(\"default\")\n    .add(\"regenerateAssets\", regenerateAssets)\n    .add(\"becomeSuperuser\", becomeSuperuser)\n    .add(\"activateTestsAssetsDebugging\", activateTestsAssetsDebugging)\n    .add(\"leaveDebugMode\", leaveDebugMode);\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"../registry\";\nimport { browser } from \"../browser/browser\";\nimport { router } from \"../browser/router\";\n\nconst commandProviderRegistry = registry.category(\"command_provider\");\n\ncommandProviderRegistry.add(\"debug\", {\n    provide: (env, options) => {\n        const result = [];\n        if (env.debug) {\n            if (!env.debug.includes(\"assets\")) {\n                result.push({\n                    action() {\n                        router.pushState({ debug: \"assets\" }, { reload: true });\n                    },\n                    category: \"debug\",\n                    name: _t(\"Activate debug mode (with assets)\"),\n                });\n            }\n            result.push({\n                action() {\n                    router.pushState({ debug: 0 }, { reload: true });\n                },\n                category: \"debug\",\n                name: _t(\"Deactivate debug mode\"),\n            });\n            result.push({\n                action() {\n                    browser.open(\"/web/tests?debug=assets\");\n                },\n                category: \"debug\",\n                name: _t(\"Run Unit Tests\"),\n            });\n        } else {\n            const debugKey = \"debug\";\n            if (options.searchValue.toLowerCase() === debugKey) {\n                result.push({\n                    action() {\n                        router.pushState({ debug: \"1\" }, { reload: true });\n                    },\n                    category: \"debug\",\n                    name: `${_t(\"Activate debug mode\")} (${debugKey})`,\n                });\n                result.push({\n                    action() {\n                        router.pushState({ debug: \"assets\" }, { reload: true });\n                    },\n                    category: \"debug\",\n                    name: `${_t(\"Activate debug mode (with assets)\")} (${debugKey})`,\n                });\n            }\n        }\n        return result;\n    },\n});\n", "export function editModelDebug(env, title, model, id) {\n    return env.services.action.doAction({\n        res_model: model,\n        res_id: id,\n        name: title,\n        type: \"ir.actions.act_window\",\n        views: [[false, \"form\"]],\n        view_mode: \"form\",\n        target: \"current\",\n    });\n}\n", "import { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { useActiveElement } from \"../ui/ui_service\";\nimport { useForwardRefToParent } from \"@web/core/utils/hooks\";\nimport { Component, onWillDestroy, useChildSubEnv, useExternalListener, useState } from \"@odoo/owl\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { makeDraggableHook } from \"../utils/draggable_hook_builder_owl\";\n\nconst useDialogDraggable = makeDraggableHook({\n    name: \"useDialogDraggable\",\n    onWillStartDrag({ ctx, addCleanup, addStyle, getRect }) {\n        const { height, width } = getRect(ctx.current.element);\n        ctx.current.container = document.createElement(\"div\");\n        addStyle(ctx.current.container, {\n            position: \"fixed\",\n            top: \"0\",\n            bottom: `${70 - height}px`,\n            left: `${70 - width}px`,\n            right: `${70 - width}px`,\n        });\n        ctx.current.element.after(ctx.current.container);\n        addCleanup(() => ctx.current.container.remove());\n    },\n    onDrop({ ctx, getRect }) {\n        const { top, left } = getRect(ctx.current.element);\n        return {\n            left: left - ctx.current.elementRect.left,\n            top: top - ctx.current.elementRect.top,\n        };\n    },\n});\n\nexport class Dialog extends Component {\n    static template = \"web.Dialog\";\n    static props = {\n        contentClass: { type: String, optional: true },\n        bodyClass: { type: String, optional: true },\n        fullscreen: { type: Boolean, optional: true },\n        footer: { type: Boolean, optional: true },\n        header: { type: Boolean, optional: true },\n        size: {\n            type: String,\n            optional: true,\n            validate: (s) => [\"sm\", \"md\", \"lg\", \"xl\", \"fs\", \"fullscreen\"].includes(s),\n        },\n        technical: { type: Boolean, optional: true },\n        title: { type: String, optional: true },\n        modalRef: { type: Function, optional: true },\n        slots: {\n            type: Object,\n            shape: {\n                default: Object, // Content is not optional\n                header: { type: Object, optional: true },\n                footer: { type: Object, optional: true },\n            },\n        },\n        withBodyPadding: { type: Boolean, optional: true },\n        onExpand: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        contentClass: \"\",\n        bodyClass: \"\",\n        fullscreen: false,\n        footer: true,\n        header: true,\n        size: \"lg\",\n        technical: true,\n        title: \"Odoo\",\n        withBodyPadding: true,\n    };\n\n    setup() {\n        this.modalRef = useForwardRefToParent(\"modalRef\");\n        useActiveElement(\"modalRef\");\n        this.data = useState(this.env.dialogData);\n        useHotkey(\"escape\", () => this.onEscape());\n        useHotkey(\n            \"control+enter\",\n            () => {\n                const btns = document.querySelectorAll(\n                    \".o_dialog:not(.o_inactive_modal) .modal-footer button\"\n                );\n                const firstVisibleBtn = Array.from(btns).find((btn) => {\n                    const styles = getComputedStyle(btn);\n                    return styles.display !== \"none\";\n                });\n                if (firstVisibleBtn) {\n                    firstVisibleBtn.click();\n                }\n            },\n            { bypassEditableProtection: true }\n        );\n        this.id = `dialog_${this.data.id}`;\n        useChildSubEnv({ inDialog: true, dialogId: this.id });\n        this.isMovable = this.props.header;\n        if (this.isMovable) {\n            this.position = useState({ left: 0, top: 0 });\n            useDialogDraggable({\n                enable: () => !this.env.isSmall,\n                ref: this.modalRef,\n                elements: \".modal-content\",\n                handle: \".modal-header\",\n                ignore: \"button, input\",\n                edgeScrolling: { enabled: false },\n                onDrop: ({ top, left }) => {\n                    this.position.left += left;\n                    this.position.top += top;\n                },\n            });\n            const throttledResize = throttleForAnimation(this.onResize.bind(this));\n            useExternalListener(window, \"resize\", throttledResize);\n        }\n        onWillDestroy(() => {\n            if (this.env.isSmall) {\n                this.data.scrollToOrigin();\n            }\n        });\n    }\n\n    get isFullscreen() {\n        return this.props.fullscreen || this.env.isSmall;\n    }\n\n    get contentStyle() {\n        if (this.isMovable) {\n            return `top: ${this.position.top}px; left: ${this.position.left}px;`;\n        }\n        return \"\";\n    }\n\n    onResize() {\n        this.position.left = 0;\n        this.position.top = 0;\n    }\n\n    onEscape() {\n        return this.dismiss();\n    }\n\n    async dismiss() {\n        if (this.data.dismiss) {\n            await this.data.dismiss();\n        }\n        return this.data.close({ dismiss: true });\n    }\n}\n", "import { Component, markRaw, reactive, useChildSubEnv, xml } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\n\nclass DialogWrapper extends Component {\n    static template = xml`<t t-component=\"props.subComponent\" t-props=\"props.subProps\" />`;\n    static props = [\"*\"];\n    setup() {\n        useChildSubEnv({ dialogData: this.props.subEnv });\n    }\n}\n\n/**\n *  @typedef {{\n *      onClose?(): void;\n *  }} DialogServiceInterfaceAddOptions\n */\n/**\n *  @typedef {{\n *      add(\n *          Component: typeof import(\"@odoo/owl\").Component,\n *          props: {},\n *          options?: DialogServiceInterfaceAddOptions\n *      ): () => void;\n *  }} DialogServiceInterface\n */\n\nexport const dialogService = {\n    dependencies: [\"overlay\"],\n    /** @returns {DialogServiceInterface} */\n    start(env, { overlay }) {\n        const stack = [];\n        let nextId = 0;\n\n        const deactivate = () => {\n            for (const subEnv of stack) {\n                subEnv.isActive = false;\n            }\n        };\n\n        const add = (dialogClass, props, options = {}) => {\n            const id = nextId++;\n            const close = (params) => remove(params);\n            const subEnv = reactive({\n                id,\n                close,\n                isActive: true,\n            });\n\n            deactivate();\n            stack.push(subEnv);\n            document.body.classList.add(\"modal-open\");\n            let isBeingClosed = false;\n\n            const scrollOrigin = { top: window.scrollY, left: window.scrollX };\n            subEnv.scrollToOrigin = () => {\n                if (!stack.length) {\n                    window.scrollTo(scrollOrigin);\n                }\n            };\n\n            const remove = overlay.add(\n                DialogWrapper,\n                {\n                    subComponent: dialogClass,\n                    subProps: markRaw({ ...props, close }),\n                    subEnv,\n                },\n                {\n                    onRemove: async (closeParams) => {\n                        if (isBeingClosed) {\n                            return;\n                        }\n                        isBeingClosed = true;\n                        await options.onClose?.(closeParams);\n                        stack.splice(\n                            stack.findIndex((d) => d.id === id),\n                            1\n                        );\n                        deactivate();\n                        if (stack.length) {\n                            stack.at(-1).isActive = true;\n                        } else {\n                            document.body.classList.remove(\"modal-open\");\n                        }\n                    },\n                    rootId: options.context?.root?.el?.getRootNode()?.host?.id,\n                }\n            );\n\n            return remove;\n        };\n\n        function closeAll(params) {\n            for (const dialog of [...stack].reverse()) {\n                dialog.close(params);\n            }\n        }\n\n        return { add, closeAll };\n    },\n};\n\nregistry.category(\"services\").add(\"dialog\", dialogService);\n", "import { shallowEqual } from \"@web/core/utils/arrays\";\nimport { evaluate, formatAST, parseExpr } from \"./py_js/py\";\nimport { toPyValue } from \"./py_js/py_utils\";\nimport { escapeRegExp } from \"@web/core/utils/strings\";\n\n/**\n * @typedef {import(\"./py_js/py_parser\").AST} AST\n * @typedef {[string | 0 | 1, string, any]} Condition\n * @typedef {(\"&\" | \"|\" | \"!\" | Condition)[]} DomainListRepr\n * @typedef {DomainListRepr | string | Domain} DomainRepr\n */\n\nexport class InvalidDomainError extends Error {}\n\n/**\n * Javascript representation of an Odoo domain\n */\nexport class Domain {\n    /**\n     * Combine various domains together with a given operator\n     * @param {DomainRepr[]} domains\n     * @param {\"AND\" | \"OR\"} operator\n     * @returns {Domain}\n     */\n    static combine(domains, operator) {\n        if (domains.length === 0) {\n            return new Domain([]);\n        }\n        const domain1 = domains[0] instanceof Domain ? domains[0] : new Domain(domains[0]);\n        if (domains.length === 1) {\n            return domain1;\n        }\n        const domain2 = Domain.combine(domains.slice(1), operator);\n        const result = new Domain([]);\n        const astValues1 = domain1.ast.value;\n        const astValues2 = domain2.ast.value;\n        const op = operator === \"AND\" ? \"&\" : \"|\";\n        const combinedAST = { type: 4 /* List */, value: astValues1.concat(astValues2) };\n        result.ast = normalizeDomainAST(combinedAST, op);\n        return result;\n    }\n\n    /**\n     * Combine various domains together with `AND` operator\n     * @param {DomainRepr[]} domains\n     * @returns {Domain}\n     */\n    static and(domains) {\n        return Domain.combine(domains, \"AND\");\n    }\n\n    /**\n     * Combine various domains together with `OR` operator\n     * @param {DomainRepr[]} domains\n     * @returns {Domain}\n     */\n    static or(domains) {\n        return Domain.combine(domains, \"OR\");\n    }\n\n    /**\n     * Return the negation of the domain\n     * @returns {Domain}\n     */\n    static not(domain) {\n        const result = new Domain(domain);\n        result.ast.value.unshift({ type: 1, value: \"!\" });\n        return result;\n    }\n\n    /**\n     * Return a new domain with `neutralized` leaves (for the leaves that are applied on the field that are part of\n     * keysToRemove).\n     * @param {DomainRepr} domain\n     * @param {string[]} keysToRemove\n     * @return {Domain}\n     */\n    static removeDomainLeaves(domain, keysToRemove) {\n        function processLeaf(elements, idx, operatorCtx, newDomain) {\n            const leaf = elements[idx];\n            if (leaf.type === 10) {\n                if (keysToRemove.includes(leaf.value[0].value)) {\n                    if (operatorCtx === \"&\") {\n                        newDomain.ast.value.push(...Domain.TRUE.ast.value);\n                    } else if (operatorCtx === \"|\") {\n                        newDomain.ast.value.push(...Domain.FALSE.ast.value);\n                    }\n                } else {\n                    newDomain.ast.value.push(leaf);\n                }\n                return 1;\n            } else if (leaf.type === 1) {\n                // Special case to avoid OR ('|') that can never resolve to true\n                if (\n                    leaf.value === \"|\" &&\n                    elements[idx + 1].type === 10 &&\n                    elements[idx + 2].type === 10 &&\n                    keysToRemove.includes(elements[idx + 1].value[0].value) &&\n                    keysToRemove.includes(elements[idx + 2].value[0].value)\n                ) {\n                    newDomain.ast.value.push(...Domain.TRUE.ast.value);\n                    return 3;\n                }\n                newDomain.ast.value.push(leaf);\n                if (leaf.value === \"!\") {\n                    return 1 + processLeaf(elements, idx + 1, \"&\", newDomain);\n                }\n                const firstLeafSkip = processLeaf(elements, idx + 1, leaf.value, newDomain);\n                const secondLeafSkip = processLeaf(\n                    elements,\n                    idx + 1 + firstLeafSkip,\n                    leaf.value,\n                    newDomain\n                );\n                return 1 + firstLeafSkip + secondLeafSkip;\n            }\n            return 0;\n        }\n\n        domain = new Domain(domain);\n        if (domain.ast.value.length === 0) {\n            return domain;\n        }\n        const newDomain = new Domain([]);\n        processLeaf(domain.ast.value, 0, \"&\", newDomain);\n        return newDomain;\n    }\n\n    /**\n     * @param {DomainRepr} [descr]\n     */\n    constructor(descr = []) {\n        if (descr instanceof Domain) {\n            /** @type {AST} */\n            return new Domain(descr.toString());\n        } else {\n            let rawAST;\n            try {\n                rawAST = typeof descr === \"string\" ? parseExpr(descr) : toAST(descr);\n            } catch (error) {\n                throw new InvalidDomainError(`Invalid domain representation: ${descr.toString()}`, {\n                    cause: error,\n                });\n            }\n            this.ast = normalizeDomainAST(rawAST);\n        }\n    }\n\n    /**\n     * Check if the set of records represented by a domain contains a record\n     * Warning: smart dates (see parseSmartDateInput) are not handled here.\n     *\n     * @param {Object} record\n     * @returns {boolean}\n     */\n    contains(record) {\n        const expr = evaluate(this.ast, record);\n        return matchDomain(record, expr);\n    }\n\n    /**\n     * @returns {string}\n     */\n    toString() {\n        return formatAST(this.ast);\n    }\n\n    /**\n     * @param {Object} context\n     * @returns {DomainListRepr}\n     */\n    toList(context) {\n        return evaluate(this.ast, context);\n    }\n\n    /**\n     * Converts the domain into a human-readable format for JSON representation.\n     * If the domain does not contain any contextual value, it is converted to a list.\n     * Otherwise, it is returned as a string.\n     *\n     * The string format is less readable due to escaped double quotes.\n     * Example: \"[\\\"&\\\",[\\\"user_id\\\",\\\"=\\\",uid],[\\\"team_id\\\",\\\"!=\\\",false]]\"\n     * @returns {DomainListRepr | string}\n     */\n    toJson() {\n        try {\n            // Attempt to evaluate the domain without context\n            const evaluatedAsList = this.toList({});\n            const evaluatedDomain = new Domain(evaluatedAsList);\n            if (evaluatedDomain.toString() === this.toString()) {\n                return evaluatedAsList;\n            }\n            return this.toString();\n        } catch {\n            // The domain couldn't be evaluated due to contextual values\n            return this.toString();\n        }\n    }\n}\n\n/** @type {Condition} */\nconst TRUE_LEAF = [1, \"=\", 1];\n/** @type {Condition} */\nconst FALSE_LEAF = [0, \"=\", 1];\nconst TRUE_DOMAIN = new Domain([TRUE_LEAF]);\nconst FALSE_DOMAIN = new Domain([FALSE_LEAF]);\n\nDomain.TRUE = TRUE_DOMAIN;\nDomain.FALSE = FALSE_DOMAIN;\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\n/**\n * @param {DomainListRepr} domain\n * @returns {AST}\n */\nfunction toAST(domain) {\n    const elems = domain.map((elem) => {\n        switch (elem) {\n            case \"!\":\n            case \"&\":\n            case \"|\":\n                return { type: 1 /* String */, value: elem };\n            default:\n                return {\n                    type: 10 /* Tuple */,\n                    value: elem.map(toPyValue),\n                };\n        }\n    });\n    return { type: 4 /* List */, value: elems };\n}\n\n/**\n * Normalizes a domain\n *\n * @param {AST} domain\n * @param {'&' | '|'} [op]\n * @returns {AST}\n */\n\nfunction normalizeDomainAST(domain, op = \"&\") {\n    if (domain.type !== 4 /* List */) {\n        if (domain.type === 10 /* Tuple */) {\n            const value = domain.value;\n            /* Tuple contains at least one Tuple and optionally string */\n            if (\n                value.findIndex((e) => e.type === 10) === -1 ||\n                !value.every((e) => e.type === 10 || e.type === 1)\n            ) {\n                throw new InvalidDomainError(\"Invalid domain AST\");\n            }\n        } else {\n            throw new InvalidDomainError(\"Invalid domain AST\");\n        }\n    }\n    if (domain.value.length === 0) {\n        return domain;\n    }\n    let expected = 1;\n    for (const child of domain.value) {\n        switch (child.type) {\n            case 1 /* String */:\n                if (child.value === \"&\" || child.value === \"|\") {\n                    expected++;\n                } else if (child.value !== \"!\") {\n                    throw new InvalidDomainError(\"Invalid domain AST\");\n                }\n                break;\n            case 4: /* list */\n            case 10 /* tuple */:\n                if (child.value.length === 3) {\n                    expected--;\n                    break;\n                }\n                throw new InvalidDomainError(\"Invalid domain AST\");\n            default:\n                throw new InvalidDomainError(\"Invalid domain AST\");\n        }\n    }\n    const values = domain.value.slice();\n    while (expected < 0) {\n        expected++;\n        values.unshift({ type: 1 /* String */, value: op });\n    }\n    if (expected > 0) {\n        throw new InvalidDomainError(\n            `invalid domain ${formatAST(domain)} (missing ${expected} segment(s))`\n        );\n    }\n    return { type: 4 /* List */, value: values };\n}\n\n/**\n * @param {Object} record\n * @param {Condition | boolean} condition\n * @returns {boolean}\n */\nfunction matchCondition(record, condition) {\n    if (typeof condition === \"boolean\") {\n        return condition;\n    }\n    const [field, operator, value] = condition;\n\n    if (typeof field === \"string\") {\n        const names = field.split(\".\");\n        if (names.length >= 2) {\n            return matchCondition(record[names[0]], [names.slice(1).join(\".\"), operator, value]);\n        }\n    }\n    let likeRegexp, ilikeRegexp;\n    if ([\"like\", \"not like\", \"ilike\", \"not ilike\"].includes(operator)) {\n        likeRegexp = new RegExp(`(.*)${escapeRegExp(value).replaceAll(\"%\", \"(.*)\")}(.*)`, \"g\");\n        ilikeRegexp = new RegExp(`(.*)${escapeRegExp(value).replaceAll(\"%\", \"(.*)\")}(.*)`, \"gi\");\n    }\n    const fieldValue = typeof field === \"number\" ? field : record[field];\n    const isNot = operator.startsWith(\"not \");\n    switch (operator) {\n        case \"=?\":\n            if ([false, null].includes(value)) {\n                return true;\n            }\n        // eslint-disable-next-line no-fallthrough\n        case \"=\":\n        case \"==\":\n            if (Array.isArray(fieldValue) && Array.isArray(value)) {\n                return shallowEqual(fieldValue, value);\n            }\n            return fieldValue === value;\n        case \"!=\":\n        case \"<>\":\n            return !matchCondition(record, [field, \"=\", value]);\n        case \"<\":\n            return fieldValue < value;\n        case \"<=\":\n            return fieldValue <= value;\n        case \">\":\n            return fieldValue > value;\n        case \">=\":\n            return fieldValue >= value;\n        case \"in\":\n        case \"not in\": {\n            const val = Array.isArray(value) ? value : [value];\n            const fieldVal = Array.isArray(fieldValue) ? fieldValue : [fieldValue];\n            return Boolean(fieldVal.some((fv) => val.includes(fv))) != isNot;\n        }\n        case \"like\":\n        case \"not like\":\n            if (fieldValue === false) {\n                return isNot;\n            }\n            return Boolean(fieldValue.match(likeRegexp)) != isNot;\n        case \"=like\":\n        case \"not =like\":\n            if (fieldValue === false) {\n                return isNot;\n            }\n            return (\n                Boolean(new RegExp(escapeRegExp(value).replace(/%/g, \".*\")).test(fieldValue)) !=\n                isNot\n            );\n        case \"ilike\":\n        case \"not ilike\":\n            if (fieldValue === false) {\n                return isNot;\n            }\n            return Boolean(fieldValue.match(ilikeRegexp)) != isNot;\n        case \"=ilike\":\n        case \"not =ilike\":\n            if (fieldValue === false) {\n                return isNot;\n            }\n            return (\n                Boolean(\n                    new RegExp(escapeRegExp(value).replace(/%/g, \".*\"), \"i\").test(fieldValue)\n                ) != isNot\n            );\n        case \"any\":\n        case \"not any\":\n            return true;\n        case \"child_of\":\n        case \"parent_of\":\n            return true;\n    }\n    throw new InvalidDomainError(\"could not match domain\");\n}\n\n/**\n * @param {Object} record\n * @returns {Object}\n */\nfunction makeOperators(record) {\n    const match = matchCondition.bind(null, record);\n    return {\n        \"!\": (x) => !match(x),\n        \"&\": (a, b) => match(a) && match(b),\n        \"|\": (a, b) => match(a) || match(b),\n    };\n}\n\n/**\n *\n * @param {Object} record\n * @param {DomainListRepr} domain\n * @returns {boolean}\n */\nfunction matchDomain(record, domain) {\n    if (domain.length === 0) {\n        return true;\n    }\n    const operators = makeOperators(record);\n    const reversedDomain = Array.from(domain).reverse();\n    const condStack = [];\n    for (const item of reversedDomain) {\n        const operator = typeof item === \"string\" && operators[item];\n        if (operator) {\n            const operands = condStack.splice(-operator.length);\n            condStack.push(operator(...operands));\n        } else {\n            condStack.push(item);\n        }\n    }\n    return matchCondition(record, condStack.pop());\n}\n", "import { Component, onWillStart, onWillUpdateProps } from \"@odoo/owl\";\nimport { CheckBox } from \"@web/core/checkbox/checkbox\";\nimport { Domain } from \"@web/core/domain\";\nimport { getDomainDisplayedOperators } from \"@web/core/domain_selector/domain_selector_operator_editor\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { ModelFieldSelector } from \"@web/core/model_field_selector/model_field_selector\";\nimport {\n    areEqualTrees,\n    condition,\n    connector,\n    formatValue,\n} from \"@web/core/tree_editor/condition_tree\";\nimport { domainFromTree } from \"@web/core/tree_editor/domain_from_tree\";\nimport { TreeEditor } from \"@web/core/tree_editor/tree_editor\";\nimport { getOperatorEditorInfo } from \"@web/core/tree_editor/tree_editor_operator_editor\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { getDefaultCondition } from \"./utils\";\n\nconst ARCHIVED_CONDITION = condition(\"active\", \"in\", [true, false]);\nconst ARCHIVED_DOMAIN = `[(\"active\", \"in\", [True, False])]`;\n\nexport class DomainSelector extends Component {\n    static template = \"web.DomainSelector\";\n    static components = { TreeEditor, CheckBox };\n    static props = {\n        domain: String,\n        resModel: String,\n        className: { type: String, optional: true },\n        defaultConnector: { type: [{ value: \"&\" }, { value: \"|\" }], optional: true },\n        isDebugMode: { type: Boolean, optional: true },\n        readonly: { type: Boolean, optional: true },\n        update: { type: Function, optional: true },\n        debugUpdate: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        isDebugMode: false,\n        readonly: true,\n        update: () => {},\n    };\n\n    setup() {\n        this.fieldService = useService(\"field\");\n        this.treeProcessor = useService(\"tree_processor\");\n\n        this.tree = null;\n        this.showArchivedCheckbox = false;\n        this.includeArchived = false;\n\n        onWillStart(() => this.onPropsUpdated(this.props));\n        onWillUpdateProps((np) => this.onPropsUpdated(np));\n    }\n\n    async onPropsUpdated(p) {\n        let domain;\n        let isSupported = true;\n        try {\n            domain = new Domain(p.domain);\n        } catch {\n            isSupported = false;\n        }\n        if (!isSupported) {\n            this.tree = null;\n            this.showArchivedCheckbox = false;\n            this.includeArchived = false;\n            return;\n        }\n\n        const [tree, { fieldDef: activeFieldDef }] = await Promise.all([\n            this.treeProcessor.treeFromDomain(p.resModel, domain, !p.isDebugMode),\n            this.fieldService.loadFieldInfo(p.resModel, \"active\"),\n        ]);\n\n        this.tree = tree;\n        this.showArchivedCheckbox = this.getShowArchivedCheckBox(Boolean(activeFieldDef), p);\n\n        this.includeArchived = false;\n        if (this.showArchivedCheckbox) {\n            if (this.tree.type === \"connector\" && this.tree.value === \"&\") {\n                this.tree.children = this.tree.children.filter((child) => {\n                    if (areEqualTrees(child, ARCHIVED_CONDITION)) {\n                        this.includeArchived = true;\n                        return false;\n                    }\n                    return true;\n                });\n                if (this.tree.children.length === 1) {\n                    this.tree = this.tree.children[0];\n                }\n            } else if (areEqualTrees(this.tree, ARCHIVED_CONDITION)) {\n                this.includeArchived = true;\n                this.tree = connector(\"&\");\n            }\n        }\n    }\n\n    getShowArchivedCheckBox(hasActiveField, props) {\n        return hasActiveField;\n    }\n\n    getDefaultCondition(fieldDefs) {\n        return getDefaultCondition(fieldDefs);\n    }\n\n    getDefaultOperator(fieldDef) {\n        return getDomainDisplayedOperators(fieldDef)[0];\n    }\n\n    getOperatorEditorInfo(fieldDef) {\n        const operators = getDomainDisplayedOperators(fieldDef);\n        return getOperatorEditorInfo(operators, fieldDef);\n    }\n\n    getPathEditorInfo(resModel, defaultCondition) {\n        const { isDebugMode } = this.props;\n        return {\n            component: ModelFieldSelector,\n            extractProps: ({ update, value: path }) => ({\n                path,\n                update,\n                resModel,\n                isDebugMode,\n                readonly: false,\n            }),\n            isSupported: (path) => [0, 1].includes(path) || typeof path === \"string\",\n            defaultValue: () => defaultCondition.path,\n            stringify: (path) => formatValue(path),\n            message: _t(\"Invalid field chain\"),\n        };\n    }\n\n    toggleIncludeArchived() {\n        this.includeArchived = !this.includeArchived;\n        this.update(this.tree);\n    }\n\n    resetDomain() {\n        this.props.update(\"[]\");\n    }\n\n    onDomainInput(domain) {\n        if (this.props.debugUpdate) {\n            this.props.debugUpdate(domain);\n        }\n    }\n\n    onDomainChange(domain) {\n        this.props.update(domain, true);\n    }\n    update(tree) {\n        const archiveDomain = this.includeArchived ? ARCHIVED_DOMAIN : `[]`;\n        const domain = tree\n            ? Domain.and([domainFromTree(tree), archiveDomain]).toString()\n            : archiveDomain;\n        this.props.update(domain);\n    }\n}\n", "export function getDomainDisplayedOperators(fieldDef) {\n    if (!fieldDef) {\n        fieldDef = {};\n    }\n    const { type, is_property } = fieldDef;\n\n    if (is_property) {\n        switch (type) {\n            case \"many2many\":\n            case \"tags\":\n                return [\"in\", \"not in\", \"set\", \"not set\"];\n            case \"many2one\":\n            case \"selection\":\n                return [\"=\", \"!=\", \"set\", \"not set\"];\n        }\n    }\n    switch (type) {\n        case \"boolean\":\n            return [\"set\", \"not set\"];\n        case \"selection\":\n            return [\"=\", \"!=\", \"in\", \"not in\", \"set\", \"not set\"];\n        case \"char\":\n        case \"text\":\n        case \"html\":\n            return [\"=\", \"!=\", \"ilike\", \"not ilike\", \"starts with\", \"set\", \"not set\"];\n        case \"date\":\n        case \"datetime\":\n            return [\"in range\", \"=\", \"<\", \">\", \"set\", \"not set\"];\n        case \"integer\":\n        case \"float\":\n        case \"monetary\":\n            return [\"=\", \"!=\", \"<\", \">\", \"between\"];\n        case \"many2one\":\n        case \"many2many\":\n        case \"one2many\":\n            return [\"in\", \"not in\", \"ilike\", \"not ilike\", \"set\", \"not set\"];\n        case \"json\":\n            return [\"=\", \"!=\", \"ilike\", \"not ilike\", \"set\", \"not set\"];\n        case \"binary\":\n        case \"properties\":\n            return [\"set\", \"not set\"];\n        case undefined:\n            return [\"=\"];\n        default:\n            return [\n                \"=\",\n                \"!=\",\n                \"<\",\n                \">\",\n                \"ilike\",\n                \"not ilike\",\n                \"like\",\n                \"not like\",\n                \"=like\",\n                \"=ilike\",\n                \"in\",\n                \"not in\",\n                \"set\",\n                \"not set\",\n            ];\n    }\n}\n", "import { getDomainDisplayedOperators } from \"@web/core/domain_selector/domain_selector_operator_editor\";\nimport { condition } from \"@web/core/tree_editor/condition_tree\";\nimport { domainFromTree } from \"@web/core/tree_editor/domain_from_tree\";\nimport { getDefaultValue } from \"@web/core/tree_editor/tree_editor_value_editors\";\nimport { getDefaultPath } from \"@web/core/tree_editor/utils\";\nimport { useService } from \"@web/core/utils/hooks\";\n\nexport function getDefaultCondition(fieldDefs) {\n    const defaultPath = getDefaultPath(fieldDefs);\n    const fieldDef = fieldDefs[defaultPath];\n    const operator = getDomainDisplayedOperators(fieldDef)[0];\n    const value = getDefaultValue(fieldDef, operator);\n    return condition(fieldDef.name, operator, value);\n}\n\nexport function getDefaultDomain(fieldDefs) {\n    return domainFromTree(getDefaultCondition(fieldDefs));\n}\n\nexport function useGetDefaultLeafDomain() {\n    const fieldService = useService(\"field\");\n    return async (resModel) => {\n        const fieldDefs = await fieldService.loadFields(resModel);\n        return getDefaultDomain(fieldDefs);\n    };\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Component, useRef, useState } from \"@odoo/owl\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { Domain } from \"@web/core/domain\";\nimport { DomainSelector } from \"@web/core/domain_selector/domain_selector\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { user } from \"@web/core/user\";\n\nexport class DomainSelectorDialog extends Component {\n    static template = \"web.DomainSelectorDialog\";\n    static components = {\n        Dialog,\n        DomainSelector,\n    };\n    static props = {\n        close: Function,\n        onConfirm: Function,\n        resModel: String,\n        className: { type: String, optional: true },\n        defaultConnector: { type: [{ value: \"&\" }, { value: \"|\" }], optional: true },\n        domain: String,\n        isDebugMode: { type: Boolean, optional: true },\n        readonly: { type: Boolean, optional: true },\n        text: { type: String, optional: true },\n        confirmButtonText: { type: String, optional: true },\n        disableConfirmButton: { type: Function, optional: true },\n        discardButtonText: { type: String, optional: true },\n        title: { type: String, optional: true },\n        context: { type: Object, optional: true },\n    };\n    static defaultProps = {\n        isDebugMode: false,\n        readonly: false,\n        context: {},\n    };\n\n    setup() {\n        this.notification = useService(\"notification\");\n        this.orm = useService(\"orm\");\n        this.state = useState({ domain: this.props.domain });\n        this.confirmButtonRef = useRef(\"confirm\");\n    }\n\n    get confirmButtonText() {\n        return this.props.confirmButtonText || _t(\"Confirm\");\n    }\n\n    get dialogTitle() {\n        return this.props.title || _t(\"Domain\");\n    }\n\n    get disabled() {\n        if (this.props.disableConfirmButton) {\n            return this.props.disableConfirmButton(this.state.domain);\n        }\n        return false;\n    }\n\n    get discardButtonText() {\n        return this.props.discardButtonText || _t(\"Discard\");\n    }\n\n    get domainSelectorProps() {\n        return {\n            className: this.props.className,\n            resModel: this.props.resModel,\n            readonly: this.props.readonly,\n            isDebugMode: this.props.isDebugMode,\n            defaultConnector: this.props.defaultConnector,\n            domain: this.state.domain,\n            update: (domain) => {\n                this.state.domain = domain;\n            },\n        };\n    }\n\n    async onConfirm() {\n        this.confirmButtonRef.el.disabled = true;\n        let domain;\n        let isValid;\n        try {\n            const evalContext = { ...user.context, ...this.props.context };\n            domain = new Domain(this.state.domain).toList(evalContext);\n        } catch {\n            isValid = false;\n        }\n        if (isValid === undefined) {\n            isValid = await rpc(\"/web/domain/validate\", {\n                model: this.props.resModel,\n                domain,\n            });\n        }\n        if (!isValid) {\n            if (this.confirmButtonRef.el) {\n                this.confirmButtonRef.el.disabled = false;\n            }\n            this.notification.add(_t(\"Domain is invalid. Please correct it\"), {\n                type: \"danger\",\n            });\n            return;\n        }\n        this.props.onConfirm(this.state.domain);\n        this.props.close();\n    }\n\n    onDiscard() {\n        this.props.close();\n    }\n}\n", "import { useComponent, useEffect, useEnv } from \"@odoo/owl\";\nimport { DROPDOWN_GROUP } from \"@web/core/dropdown/dropdown_group\";\n\n/**\n * @typedef DropdownGroupState\n * @property {boolean} isInGroup\n * @property {boolean} isOpen\n */\n\n/**\n * Will add (and remove) a dropdown from a parent\n * DropdownGroup component, allowing it to know\n * if it's in a group and if the group is open.\n *\n * @returns {DropdownGroupState}\n */\nexport function useDropdownGroup() {\n    const env = useEnv();\n\n    const group = {\n        isInGroup: DROPDOWN_GROUP in env,\n        get isOpen() {\n            return this.isInGroup && [...env[DROPDOWN_GROUP]].some((dropdown) => dropdown.isOpen);\n        },\n    };\n\n    if (group.isInGroup) {\n        const dropdown = useComponent();\n        useEffect(() => {\n            env[DROPDOWN_GROUP].add(dropdown.state);\n            return () => env[DROPDOWN_GROUP].delete(dropdown.state);\n        });\n    }\n\n    return group;\n}\n", "import { EventBus, onWillDestroy, useChildSubEnv, useEffect, useEnv } from \"@odoo/owl\";\nimport { localization } from \"@web/core/l10n/localization\";\nimport { useBus, useService } from \"@web/core/utils/hooks\";\nimport { effect } from \"@web/core/utils/reactive\";\n\nexport const DROPDOWN_NESTING = Symbol(\"dropdownNesting\");\nconst BUS = new EventBus();\n\nclass DropdownNestingState {\n    constructor({ parent, close }) {\n        this._isOpen = false;\n        this.parent = parent;\n        this.children = new Set();\n        this.close = close;\n\n        parent?.children.add(this);\n    }\n\n    set isOpen(value) {\n        this._isOpen = value;\n        if (this._isOpen) {\n            BUS.trigger(\"dropdown-opened\", this);\n        }\n    }\n\n    get isOpen() {\n        return this._isOpen;\n    }\n\n    remove() {\n        this.parent?.children.delete(this);\n    }\n\n    closeAllParents() {\n        this.close();\n        if (this.parent) {\n            this.parent.closeAllParents();\n        }\n    }\n\n    closeChildren() {\n        this.children.forEach((child) => child.close());\n    }\n\n    shouldIgnoreChanges(other) {\n        return (\n            other === this ||\n            other.activeEl !== this.activeEl ||\n            [...this.children].some((child) => child.shouldIgnoreChanges(other))\n        );\n    }\n\n    handleChange(other) {\n        // Prevents closing the dropdown when a change is coming from itself or from a children.\n        if (this.shouldIgnoreChanges(other)) {\n            return;\n        }\n\n        if (other.isOpen && this.isOpen) {\n            this.close();\n        }\n    }\n}\n\n/**\n * This hook is used to manage communication between dropdowns.\n *\n * When a dropdown is open, every other dropdown that is not a parent\n * is closed. It also uses the current's ui active element to only\n * close itself when the active element is the same as the current\n * dropdown to separate dropdowns in different dialogs.\n *\n * @param {import(\"@web/core/dropdown/dropdown_hooks\").DropdownState} state\n * @returns\n */\nexport function useDropdownNesting(state) {\n    const env = useEnv();\n    const current = new DropdownNestingState({\n        parent: env[DROPDOWN_NESTING],\n        close: () => state.close(),\n    });\n\n    // Set up UI active element related behavior ---------------------------\n    const uiService = useService(\"ui\");\n    useEffect(\n        () => {\n            Promise.resolve().then(() => {\n                current.activeEl = uiService.activeElement;\n            });\n        },\n        () => []\n    );\n\n    useChildSubEnv({ [DROPDOWN_NESTING]: current });\n    useBus(BUS, \"dropdown-opened\", ({ detail: other }) => current.handleChange(other));\n\n    effect(\n        (state) => {\n            current.isOpen = state.isOpen;\n        },\n        [state]\n    );\n\n    onWillDestroy(() => {\n        current.remove();\n    });\n\n    const isDropdown = (target) => target && target.classList.contains(\"o-dropdown\");\n    const isRTL = () => localization.direction === \"rtl\";\n\n    return {\n        get hasParent() {\n            return Boolean(current.parent);\n        },\n        /**@type {import(\"@web/core/navigation/navigation\").NavigationOptions} */\n        navigationOptions: {\n            onUpdated: (navigator) => {\n                if (current.parent && !navigator.activeItem) {\n                    navigator.items[0]?.setActive();\n                }\n            },\n            hotkeys: {\n                escape: () => current.close(),\n                arrowleft: {\n                    isAvailable: () => true,\n                    callback: (navigator) => {\n                        if (isRTL() && isDropdown(navigator.activeItem?.target)) {\n                            navigator.activeItem?.select();\n                        } else if (current.parent) {\n                            current.close();\n                        }\n                    },\n                },\n                arrowright: {\n                    isAvailable: () => true,\n                    callback: (navigator) => {\n                        if (isRTL() && current.parent) {\n                            current.close();\n                        } else if (isDropdown(navigator.activeItem?.target)) {\n                            navigator.activeItem?.select();\n                        }\n                    },\n                },\n            },\n        },\n    };\n}\n", "import { Component, onMounted, onRendered, onWillDestroy, onWillStart, xml } from \"@odoo/owl\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\n\nexport class DropdownPopover extends Component {\n    static components = { DropdownItem };\n    static template = xml`\n        <t t-if=\"this.props.items\">\n            <t t-foreach=\"this.props.items\" t-as=\"item\" t-key=\"this.getKey(item, item_index)\">\n                <DropdownItem class=\"item.class\" onSelected=\"() => item.onSelected()\" t-out=\"item.label\"/>\n            </t>\n        </t>\n        <t t-slot=\"content\" />\n    `;\n    static props = {\n        // Popover service\n        close: { type: Function, optional: true },\n\n        // Events & Handlers\n        beforeOpen: { type: Function, optional: true },\n        onOpened: { type: Function, optional: true },\n        onClosed: { type: Function, optional: true },\n\n        // Rendering & Context\n        refresher: Object,\n        slots: Object,\n        items: { type: Array, optional: true },\n    };\n\n    setup() {\n        onRendered(() => {\n            // Note that the Dropdown component and the DropdownPopover component\n            // are not in the same context.\n            // So when the Dropdown component is re-rendered, the DropdownPopover\n            // component must also re-render itself.\n            // This is why we subscribe to this reactive, which is changed when\n            // the Dropdown component is re-rendered.\n            this.props.refresher.token;\n        });\n\n        onWillStart(async () => {\n            await this.props.beforeOpen?.();\n        });\n\n        onMounted(() => {\n            this.props.onOpened?.();\n        });\n\n        onWillDestroy(() => {\n            this.props.onClosed?.();\n        });\n    }\n\n    getKey(item, index) {\n        return \"id\" in item ? item.id : index;\n    }\n}\n", "import { Component, onPatched, useState } from \"@odoo/owl\";\n\nexport const ACCORDION = Symbol(\"Accordion\");\nexport class AccordionItem extends Component {\n    static template = \"web.AccordionItem\";\n    static components = {};\n    static props = {\n        slots: {\n            type: Object,\n            shape: {\n                default: {},\n            },\n        },\n        description: String,\n        selected: {\n            type: Boolean,\n            optional: true,\n        },\n        class: {\n            type: String,\n            optional: true,\n        },\n    };\n    static defaultProps = {\n        class: \"\",\n        selected: false,\n    };\n\n    setup() {\n        this.state = useState({\n            open: false,\n        });\n        this.parentComponent = this.env[ACCORDION];\n        onPatched(() => {\n            this.parentComponent?.accordionStateChanged?.();\n        });\n    }\n}\n", "import { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\n\nexport class CheckboxItem extends DropdownItem {\n    static template = \"web.CheckboxItem\";\n    static props = {\n        ...DropdownItem.props,\n        checked: {\n            type: Boolean,\n            optional: false,\n        },\n    };\n}\n", "import {\n    Component,\n    onMounted,\n    onRendered,\n    onWillUpdateProps,\n    reactive,\n    status,\n    useEffect,\n    xml,\n} from \"@odoo/owl\";\nimport { useDropdownGroup } from \"@web/core/dropdown/_behaviours/dropdown_group_hook\";\nimport { useDropdownNesting } from \"@web/core/dropdown/_behaviours/dropdown_nesting\";\nimport { DropdownPopover } from \"@web/core/dropdown/_behaviours/dropdown_popover\";\nimport { useDropdownState } from \"@web/core/dropdown/dropdown_hooks\";\nimport { useNavigation } from \"@web/core/navigation/navigation\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { mergeClasses } from \"@web/core/utils/classname\";\nimport { useChildRef, useService } from \"@web/core/utils/hooks\";\nimport { deepMerge } from \"@web/core/utils/objects\";\nimport { effect } from \"@web/core/utils/reactive\";\nimport { utils } from \"@web/core/ui/ui_service\";\nimport { hasTouch } from \"@web/core/browser/feature_detection\";\n\nfunction getFirstElementOfNode(node) {\n    if (!node) {\n        return null;\n    }\n    if (node.el) {\n        return node.el.nodeType === Node.ELEMENT_NODE ? node.el : null;\n    }\n    if (node.bdom || node.child) {\n        return getFirstElementOfNode(node.bdom || node.child);\n    }\n    if (node.children) {\n        for (const child of node.children) {\n            const el = getFirstElementOfNode(child);\n            if (el) {\n                return el;\n            }\n        }\n    }\n    return null;\n}\n\n/**\n * The Dropdown component allows to define a menu that will\n * show itself when a target is toggled.\n *\n * Items are defined using DropdownItems. Dropdowns are\n * also allowed as items to be able to create nested\n * dropdown menus.\n */\nexport class Dropdown extends Component {\n    static template = xml`<t t-slot=\"default\"/>`;\n    static components = {};\n    static props = {\n        menuClass: { optional: true },\n        position: { type: String, optional: true },\n        slots: {\n            type: Object,\n            shape: {\n                default: { optional: true },\n                content: { optional: true },\n            },\n        },\n\n        items: {\n            optional: true,\n            type: Array,\n            elements: {\n                type: Object,\n                shape: {\n                    label: String,\n                    onSelected: Function,\n                    class: { optional: true },\n                    \"*\": true,\n                },\n            },\n        },\n\n        menuRef: { type: Function, optional: true }, // to be used with useChildRef\n        disabled: { type: Boolean, optional: true },\n        holdOnHover: { type: Boolean, optional: true },\n        focusToggleOnClosed: { type: Boolean, optional: true },\n\n        beforeOpen: { type: Function, optional: true },\n        onOpened: { type: Function, optional: true },\n        onStateChanged: { type: Function, optional: true },\n\n        /** Manual state handling, @see useDropdownState */\n        state: {\n            type: Object,\n            shape: {\n                isOpen: Boolean,\n                close: Function,\n                open: Function,\n                \"*\": true,\n            },\n            optional: true,\n        },\n        manual: { type: Boolean, optional: true },\n\n        /** When true, do not add optional styling css classes on the target*/\n        noClasses: { type: Boolean, optional: true },\n\n        /**\n         * Override the internal navigation hook options\n         * @type {import(\"@web/core/navigation/navigation\").NavigationOptions}\n         */\n        navigationOptions: { type: Object, optional: true },\n        bottomSheet: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        disabled: false,\n        holdOnHover: false,\n        focusToggleOnClosed: true,\n        menuClass: \"\",\n        state: undefined,\n        noClasses: false,\n        navigationOptions: {},\n        bottomSheet: true,\n    };\n\n    setup() {\n        this.menuRef = this.props.menuRef || useChildRef();\n\n        this.state = this.props.state || useDropdownState();\n        this.nesting = useDropdownNesting(this.state);\n        this.group = useDropdownGroup();\n\n        this.navigation = useNavigation(this.menuRef, {\n            shouldRegisterHotkeys: false,\n            isNavigationAvailable: () => this.state.isOpen,\n            getItems: () => {\n                if (this.state.isOpen && this.menuRef.el) {\n                    return this.menuRef.el.querySelectorAll(\n                        \":scope .o-navigable, :scope .o-dropdown\"\n                    );\n                } else {\n                    return [];\n                }\n            },\n            // Using deepMerge allows to keep entries of both option.hotkeys\n            ...deepMerge(this.nesting.navigationOptions, this.props.navigationOptions),\n        });\n\n        this.uiService = useService(\"ui\");\n\n        const getPosition = () => this.position;\n        const options = {\n            animation: false,\n            arrow: false,\n            closeOnClickAway: (target) => this.popoverCloseOnClickAway(target),\n            closeOnEscape: false, // Handled via navigation and prevents closing root of nested dropdown\n            env: this.__owl__.childEnv,\n            holdOnHover: this.props.holdOnHover,\n            onClose: () => this.state.close(),\n            onPositioned: (el, { direction }) => this.setTargetDirectionClass(direction),\n            popoverClass: mergeClasses(\n                \"o-dropdown--menu dropdown-menu mx-0\",\n                { \"o-dropdown--menu-submenu\": this.hasParent },\n                this.props.menuClass\n            ),\n            role: \"menu\",\n            get position() {\n                return getPosition();\n            },\n            ref: this.menuRef,\n            setActiveElement: false,\n        };\n        if (this.isBottomSheet) {\n            Object.assign(options, {\n                useBottomSheet: true,\n                class: mergeClasses(\"o-dropdown--menu dropdown-menu show\", this.props.menuClass),\n            });\n        }\n        this.popover = usePopover(DropdownPopover, options);\n\n        // As the popover is in another context we need to force\n        // its re-rendering when the dropdown re-renders\n        onRendered(() => (this.popoverRefresher ? this.popoverRefresher.token++ : null));\n\n        onMounted(() => this.onStateChanged(this.state));\n        effect((state) => this.onStateChanged(state), [this.state]);\n\n        useEffect(\n            (target) => this.setTargetElement(target),\n            () => [this.target]\n        );\n\n        onWillUpdateProps(({ disabled }) => {\n            if (disabled) {\n                this.closePopover();\n            }\n        });\n    }\n\n    get isBottomSheet() {\n        return utils.isSmall() && hasTouch() && this.props.bottomSheet;\n    }\n\n    /** @type {string} */\n    get position() {\n        return this.props.position || (this.hasParent ? \"right-start\" : \"bottom-start\");\n    }\n\n    get hasParent() {\n        return this.nesting.hasParent;\n    }\n\n    /** @type {HTMLElement|null} */\n    get target() {\n        const target = getFirstElementOfNode(this.__owl__.bdom);\n        if (!target) {\n            throw new Error(\n                \"Could not find a valid dropdown toggler, prefer a single html element and put any dynamic content inside of it.\"\n            );\n        }\n        return target;\n    }\n\n    handleClick(event) {\n        if (this.props.disabled) {\n            return;\n        }\n\n        event.stopPropagation();\n        if (this.state.isOpen && !this.hasParent) {\n            this.state.close();\n        } else {\n            this.state.open();\n        }\n    }\n\n    handleMouseEnter() {\n        if (this.props.disabled) {\n            return;\n        }\n\n        if (this.hasParent || this.group.isOpen) {\n            this.target.focus();\n            this.state.open();\n        }\n    }\n\n    onStateChanged(state) {\n        if (state.isOpen) {\n            this.openPopover();\n        } else {\n            this.closePopover();\n        }\n    }\n\n    popoverCloseOnClickAway(target) {\n        const rootNode = target.getRootNode();\n        if (rootNode instanceof ShadowRoot) {\n            target = rootNode.host;\n        }\n        return this.uiService.getActiveElementOf(target) === this.activeEl;\n    }\n\n    setTargetElement(target) {\n        if (!target) {\n            return;\n        }\n\n        target.ariaExpanded = false;\n        const optionalClasses = [];\n        const requiredClasses = [];\n        optionalClasses.push(\"o-dropdown\");\n\n        if (this.hasParent) {\n            requiredClasses.push(\"o-dropdown--has-parent\");\n        }\n\n        const tagName = target.tagName.toLowerCase();\n        if (![\"input\", \"textarea\", \"table\", \"thead\", \"tbody\", \"tr\", \"th\", \"td\"].includes(tagName)) {\n            optionalClasses.push(\"dropdown-toggle\");\n            if (this.hasParent) {\n                optionalClasses.push(\"o-dropdown-item\", \"dropdown-item\");\n                requiredClasses.push(\"o-navigable\");\n\n                if (!target.classList.contains(\"o-dropdown--no-caret\")) {\n                    requiredClasses.push(\"o-dropdown-caret\");\n                }\n            }\n        }\n\n        target.classList.add(...requiredClasses);\n        if (!this.props.noClasses) {\n            target.classList.add(...optionalClasses);\n        }\n\n        this.defaultDirection = this.position.split(\"-\")[0];\n        this.setTargetDirectionClass(this.defaultDirection);\n\n        if (!this.props.manual) {\n            target.addEventListener(\"click\", this.handleClick.bind(this));\n            target.addEventListener(\"mouseenter\", this.handleMouseEnter.bind(this));\n\n            return () => {\n                target.removeEventListener(\"click\", this.handleClick.bind(this));\n                target.removeEventListener(\"mouseenter\", this.handleMouseEnter.bind(this));\n            };\n        }\n    }\n\n    setTargetDirectionClass(direction) {\n        if (!this.target || this.props.noClasses) {\n            return;\n        }\n        const directionClasses = {\n            bottom: \"dropdown\",\n            top: \"dropup\",\n            left: \"dropstart\",\n            right: \"dropend\",\n        };\n        this.target.classList.remove(...Object.values(directionClasses));\n        this.target.classList.add(directionClasses[direction]);\n    }\n\n    openPopover() {\n        if (this.popover.isOpen || status(this) !== \"mounted\") {\n            return;\n        }\n        if (!this.target || !this.target.isConnected) {\n            this.state.close();\n            return;\n        }\n\n        this.popoverRefresher = reactive({ token: 0 });\n        const props = {\n            beforeOpen: () => this.props.beforeOpen?.(),\n            onOpened: () => this.onOpened(),\n            onClosed: () => this.onClosed(),\n            refresher: this.popoverRefresher,\n            items: this.props.items,\n            slots: this.props.slots,\n        };\n        this.popover.open(this.target, props);\n    }\n\n    closePopover() {\n        this.popover.close();\n        if (this.props.focusToggleOnClosed && !this.group.isInGroup) {\n            this._focusedElBeforeOpen?.focus();\n            this._focusedElBeforeOpen = undefined;\n        }\n    }\n\n    onOpened() {\n        this._focusedElBeforeOpen = document.activeElement;\n        this.activeEl = this.uiService.activeElement;\n        this.navigation.registerHotkeys();\n        this.navigation.update();\n        this.props.onOpened?.();\n        this.props.onStateChanged?.(true);\n\n        if (this.target) {\n            this.target.ariaExpanded = true;\n            this.target.classList.add(\"show\");\n        }\n\n        this.observer = new MutationObserver(() => this.navigation.update());\n        this.observer.observe(this.menuRef.el, {\n            childList: true,\n            subtree: true,\n        });\n    }\n\n    onClosed() {\n        this.navigation.unregisterHotkeys();\n        this.navigation.update();\n        this.props.onStateChanged?.(false);\n        delete this.activeEl;\n\n        if (this.target) {\n            this.target.ariaExpanded = false;\n            this.target.classList.remove(\"show\");\n            this.setTargetDirectionClass(this.defaultDirection);\n        }\n\n        if (this.observer) {\n            this.observer.disconnect();\n            this.observer = null;\n        }\n    }\n}\n", "import { Component, onWillDestroy, useChildSubEnv, xml } from \"@odoo/owl\";\n\nconst GROUPS = new Map();\n\nfunction getGroup(id) {\n    if (!GROUPS.has(id)) {\n        GROUPS.set(id, {\n            group: new Set(),\n            count: 0,\n        });\n    }\n    GROUPS.get(id).count++;\n    return GROUPS.get(id).group;\n}\n\nfunction removeGroup(id) {\n    const groupData = GROUPS.get(id);\n    groupData.count--;\n    if (groupData.count <= 0) {\n        GROUPS.delete(id);\n    }\n}\n\nexport const DROPDOWN_GROUP = Symbol(\"dropdownGroup\");\nexport class DropdownGroup extends Component {\n    static template = xml`<t t-slot=\"default\"/>`;\n    static props = {\n        group: { type: String, optional: true },\n        slots: Object,\n    };\n\n    setup() {\n        if (this.props.group) {\n            const group = getGroup(this.props.group);\n            onWillDestroy(() => removeGroup(this.props.group));\n            useChildSubEnv({ [DROPDOWN_GROUP]: group });\n        } else {\n            useChildSubEnv({ [DROPDOWN_GROUP]: new Set() });\n        }\n    }\n}\n", "import { useEnv, useState } from \"@odoo/owl\";\nimport { DROPDOWN_NESTING } from \"@web/core/dropdown/_behaviours/dropdown_nesting\";\nimport { Reactive } from \"@web/core/utils/reactive\";\n\n/**\n * Represents the state of a dropdown.\n * In order to use it, pass the state instance to the dropdown component, i.e.:\n *  <Dropdown state=\"dropdownState\" ...>...</Dropdown>\n * @param {Object} callbacks\n * @param {Function} callbacks.onOpen\n * @param {Function} callbacks.onClose\n */\nexport class DropdownState extends Reactive {\n    isOpen = false;\n    constructor({ onOpen, onClose } = {}) {\n        super();\n        this._onOpen = onOpen;\n        this._onClose = onClose;\n    }\n    open() {\n        this.isOpen = true;\n        this._onOpen?.();\n    }\n    close() {\n        this.isOpen = false;\n        this._onClose?.();\n    }\n}\n\n/**\n * Hook used to interact with the Dropdown state and to subscribe to changes.\n * @param {Object} callbacks\n * @param {Function} callbacks.onOpen\n * @param {Function} callbacks.onClose\n * @returns {DropdownState}\n */\nexport function useDropdownState({ onOpen, onClose } = {}) {\n    return useState(new DropdownState({ onOpen, onClose }));\n}\n\n/**\n * Can be used by components to have some control\n * how and when a wrapping dropdown should close.\n */\nexport function useDropdownCloser() {\n    const env = useEnv();\n    const dropdown = env[DROPDOWN_NESTING];\n    return {\n        close: () => dropdown?.close(),\n        closeChildren: () => dropdown?.closeChildren(),\n        closeAll: () => dropdown?.closeAllParents(),\n    };\n}\n", "import { Component } from \"@odoo/owl\";\nimport { useDropdownCloser } from \"@web/core/dropdown/dropdown_hooks\";\n\nconst ClosingMode = {\n    None: \"none\",\n    ClosestParent: \"closest\",\n    AllParents: \"all\",\n};\n\nexport class DropdownItem extends Component {\n    static template = \"web.DropdownItem\";\n    static props = {\n        tag: {\n            type: String,\n            optional: true,\n        },\n        class: {\n            type: [String, Object],\n            optional: true,\n        },\n        onSelected: {\n            type: Function,\n            optional: true,\n        },\n        closingMode: {\n            type: ClosingMode,\n            optional: true,\n        },\n        attrs: {\n            type: Object,\n            optional: true,\n        },\n        slots: { Object, optional: true },\n    };\n    static defaultProps = {\n        closingMode: ClosingMode.AllParents,\n        attrs: {},\n    };\n\n    setup() {\n        this.dropdownControl = useDropdownCloser();\n    }\n\n    onClick(ev) {\n        if (this.props.attrs && this.props.attrs.href) {\n            ev.preventDefault();\n        }\n        this.props.onSelected?.(ev);\n        switch (this.props.closingMode) {\n            case ClosingMode.ClosestParent:\n                this.dropdownControl.close();\n                break;\n            case ClosingMode.AllParents:\n                this.dropdownControl.closeAll();\n                break;\n        }\n    }\n}\n", "import { Component, useEffect, useRef, useState } from \"@odoo/owl\";\n\nexport class Dropzone extends Component {\n    static props = {\n        extraClass: { type: String, optional: true },\n        onDrop: { type: Function, optional: true },\n        ref: [Object, Function],\n        slots: { type: Object, optional: true },\n    };\n    static template = \"web.Dropzone\";\n\n    setup() {\n        super.setup();\n        this.root = useRef(\"root\");\n        this.state = useState({\n            isDraggingInside: false,\n        });\n        useEffect(() => {\n            const { top, left, width, height } = this.props.ref.el.getBoundingClientRect();\n            this.root.el.style = `top:${top}px;left:${left}px;width:${width}px;height:${height}px;`;\n        });\n    }\n}\n", "import { Dropzone } from \"@web/core/dropzone/dropzone\";\nimport { useEffect, useExternalListener } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\n\n/**\n * @param {Ref} targetRef - Element on which to place the dropzone.\n * @param {Class} dropzoneComponent - Class used to instantiate the dropzone component.\n * @param {Object} dropzoneComponentProps - Props given to the instantiated dropzone component.\n * @param {function} isDropzoneEnabled - Function that determines whether the dropzone should be enabled.\n */\nexport function useCustomDropzone(targetRef, dropzoneComponent, dropzoneComponentProps, isDropzoneEnabled = () => true) {\n    const overlayService = useService(\"overlay\");\n    const uiService = useService(\"ui\");\n\n    let dragCount = 0;\n    let hasTarget = false;\n    let removeDropzone = false;\n\n    useExternalListener(document, \"dragenter\", onDragEnter, { capture: true });\n    useExternalListener(document, \"dragleave\", onDragLeave, { capture: true });\n    // Prevents the browser to open or download the file when it is dropped\n    // outside of the dropzone.\n    useExternalListener(window, \"dragover\", (ev) => {\n        if (ev.dataTransfer && ev.dataTransfer.types.includes(\"Files\")) {\n            ev.preventDefault();\n        }\n    });\n    useExternalListener(\n        window,\n        \"drop\",\n        (ev) => {\n            if (ev.dataTransfer && ev.dataTransfer.types.includes(\"Files\")) {\n                ev.preventDefault();\n            }\n            dragCount = 0;\n            updateDropzone();\n        },\n        { capture: true }\n    );\n\n    function updateDropzone() {\n        const hasDropzone = !!removeDropzone;\n        const isTargetInActiveElement = uiService.activeElement.contains(targetRef.el);\n        const shouldDisplayDropzone = dragCount && hasTarget && isTargetInActiveElement && isDropzoneEnabled();\n\n        if (shouldDisplayDropzone && !hasDropzone) {\n            removeDropzone = overlayService.add(dropzoneComponent, {\n                ref: targetRef,\n                ...dropzoneComponentProps\n            });\n        }\n        if (!shouldDisplayDropzone && hasDropzone) {\n            removeDropzone();\n            removeDropzone = false;\n        }\n    }\n\n    function onDragEnter(ev) {\n        if (dragCount || (ev.dataTransfer && ev.dataTransfer.types.includes(\"Files\"))) {\n            dragCount++;\n            updateDropzone();\n        }\n    }\n\n    function onDragLeave() {\n        if (dragCount) {\n            dragCount--;\n            updateDropzone();\n        }\n    }\n\n    useEffect(\n        (el) => {\n            hasTarget = !!el;\n            updateDropzone();\n        },\n        () => [targetRef.el]\n    );\n}\n\n/**\n * @param {Ref} targetRef - Element on which to place the dropzone.\n * @param {function} onDrop - Callback function called when the user drops a file on the dropzone.\n * @param {string} extraClass - Classes that will be added to the standard `Dropzone` component.\n * @param {function} isDropzoneEnabled - Function that determines whether the dropzone should be enabled.\n */\nexport function useDropzone(targetRef, onDrop, extraClass, isDropzoneEnabled = () => true) {\n    const dropzoneComponent = Dropzone;\n    const dropzoneComponentProps = { extraClass, onDrop };\n    useCustomDropzone(targetRef, dropzoneComponent, dropzoneComponentProps, isDropzoneEnabled);\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { user } from \"@web/core/user\";\nimport { RainbowMan } from \"./rainbow_man\";\n\nconst effectRegistry = registry.category(\"effects\");\n\n// -----------------------------------------------------------------------------\n// RainbowMan effect\n// -----------------------------------------------------------------------------\n\n/**\n * Handles effect of type \"rainbow_man\". If the effects aren't disabled, returns\n * the RainbowMan component to instantiate and its props. If the effects are\n * disabled, displays the message in a notification.\n *\n * @param {Object} env\n * @param {Object} [params={}]\n * @param {string} [params.message=\"Well Done!\"]\n *    The message in the notice the rainbowman holds or the content of the notification if effects are disabled\n *    Can be a simple a string\n *    Can be a string representation of html (prefer component if you want interactions in the DOM)\n * @param {string} [params.img_url=\"/web/static/img/smile.svg\"]\n *    The url of the image to display inside the rainbow\n * @param {\"slow\"|\"medium\"|\"fast\"|\"no\"} [params.fadeout=\"medium\"]\n *    Delay for rainbowman to disappear\n *    'fast' will make rainbowman dissapear quickly\n *    'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer)\n *    'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman\n * @param {typeof import(\"@odoo/owl\").Component} [params.Component]\n *    Custom Component class to instantiate inside the Rainbow Man\n * @param {Object} [params.props]\n *    If params.Component is given, its props can be passed with this argument\n */\nfunction rainbowMan(env, params = {}) {\n    let message = params.message;\n    if (message instanceof Element) {\n        console.log(\n            \"Providing an HTML element to an effect is deprecated. Note that all event handlers will be lost.\"\n        );\n        message = message.outerHTML;\n    } else if (!message) {\n        message = _t(\"Well Done!\");\n    }\n    if (user.showEffect) {\n        /** @type {import(\"./rainbow_man\").RainbowManProps} */\n        const props = {\n            imgUrl: params.img_url || \"/web/static/img/smile.svg\",\n            fadeout: params.fadeout || \"medium\",\n            message,\n            Component: params.Component,\n            props: params.props,\n        };\n        return { Component: RainbowMan, props };\n    }\n    env.services.notification.add(message);\n}\neffectRegistry.add(\"rainbow_man\", rainbowMan);\n\n// -----------------------------------------------------------------------------\n// Effect service\n// -----------------------------------------------------------------------------\n\nexport const effectService = {\n    dependencies: [\"overlay\"],\n    start(env, { overlay }) {\n        /**\n         * @param {Object} [params] various params depending on the type of effect\n         * @param {string} [params.type=\"rainbow_man\"] the effect to display\n         */\n        const add = (params = {}) => {\n            const type = params.type || \"rainbow_man\";\n            const effect = effectRegistry.get(type);\n            const { Component, props } = effect(env, params) || {};\n            if (Component) {\n                const remove = overlay.add(Component, {\n                    ...props,\n                    close: () => remove(),\n                });\n            }\n        };\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"effect\", effectService);\n", "import { browser } from \"@web/core/browser/browser\";\n\nimport { Component, useEffect, useExternalListener, useState } from \"@odoo/owl\";\n\n/**\n * @typedef Common\n * @property {string} [fadeout='medium'] Delay for rainbowman to disappear.\n *  - 'fast' will make rainbowman dissapear quickly,\n *  - 'medium' and 'slow' will wait little longer before disappearing\n *      (can be used when props.message is longer),\n *  - 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman\n * @property {string} [imgUrl] URL of the image to be displayed\n *\n * @typedef Simple\n * @property {string} message Message to be displayed on rainbowman card\n *\n * @typedef Custom\n * @property {typeof import(\"@odoo/owl\").Component} Component\n * @property {any} [props]\n *\n * @typedef {Common & (Simple | Custom)} RainbowManProps\n */\n\n/**\n * The RainbowMan Component is meant to display a 'fun/rewarding' message.  For\n * example, when the user marked a large deal as won, or when he cleared its inbox.\n *\n * This component is mostly a picture and a message with a rainbow animation around.\n * If you want to display a RainbowMan, you probably do not want to do it by\n * importing this file.  The usual way to do that would be to use the effect\n * service.\n */\nexport class RainbowMan extends Component {\n    static template = \"web.RainbowMan\";\n    static rainbowFadeouts = { slow: 4500, medium: 3500, fast: 2000, no: false };\n    static props = {\n        fadeout: String,\n        close: Function,\n        message: String,\n        imgUrl: String,\n        Component: { type: Function, optional: true },\n        props: { type: Object, optional: true },\n    };\n\n    setup() {\n        useExternalListener(document.body, \"click\", this.closeRainbowMan);\n        this.state = useState({ isFading: false });\n        this.delay = RainbowMan.rainbowFadeouts[this.props.fadeout];\n        if (this.delay) {\n            useEffect(\n                () => {\n                    const timeout = browser.setTimeout(() => {\n                        this.state.isFading = true;\n                    }, this.delay);\n                    return () => browser.clearTimeout(timeout);\n                },\n                () => []\n            );\n        }\n    }\n\n    onAnimationEnd(ev) {\n        if (this.delay && ev.animationName === \"reward-fading-reverse\") {\n            ev.stopPropagation();\n            this.closeRainbowMan();\n        }\n    }\n\n    closeRainbowMan() {\n        this.props.close();\n    }\n}\n", "import { markEventHandled } from \"@web/core/utils/misc\";\n\nimport {\n    App,\n    Component,\n    onMounted,\n    onPatched,\n    onWillPatch,\n    onWillStart,\n    onWillUnmount,\n    reactive,\n    useComponent,\n    useEffect,\n    useExternalListener,\n    useRef,\n    useState,\n    xml,\n} from \"@odoo/owl\";\n\nimport { loadBundle } from \"@web/core/assets\";\nimport { _t, appTranslateFn } from \"@web/core/l10n/translation\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { fuzzyLookup } from \"@web/core/utils/search\";\nimport { useAutofocus, useService } from \"@web/core/utils/hooks\";\nimport { isMobileOS } from \"@web/core/browser/feature_detection\";\nimport { Deferred } from \"../utils/concurrency\";\nimport { Dialog } from \"../dialog/dialog\";\nimport { getTemplate } from \"@web/core/templates\";\n\n/**\n * @typedef Emoji\n * @property {string} category\n * @property {string} codepoints the emoji itself to be displayed\n * @property {string[]} emoticons string substitution (eg: \":p\")\n * @property {string[]} keywords\n * @property {string} name\n * @property {string[]} shortcodes\n */\n\nexport function useEmojiPicker(...args) {\n    return usePicker(EmojiPicker, ...args);\n}\n\nexport const loader = reactive({\n    loadEmoji: () => loadBundle(\"web.assets_emoji\"),\n    /** @type {{ emojiValueToShortcodes: Object<string, string[]>, emojiRegex: RegExp} }} */\n    loaded: undefined,\n});\n\n/** @returns {Promise<{ categories: Object[], emojis: Emoji[] }>\")} */\nexport async function loadEmoji() {\n    const res = { categories: [], emojis: [] };\n    try {\n        await loader.loadEmoji();\n        const { getCategories, getEmojis } = odoo.loader.modules.get(\n            \"@web/core/emoji_picker/emoji_data\"\n        );\n        res.categories = getCategories();\n        res.emojis = getEmojis();\n        return res;\n    } catch {\n        // Could be intentional (tour ended successfully while emoji still loading)\n        return res;\n    } finally {\n        if (!loader.loaded) {\n            const emojiValueToShortcodes = {};\n            for (const emoji of res.emojis) {\n                emojiValueToShortcodes[emoji.codepoints] = emoji.shortcodes;\n            }\n            loader.loaded = {\n                emojiValueToShortcodes,\n                emojiRegex: new RegExp(\n                    Object.keys(emojiValueToShortcodes).length\n                        ? Object.keys(emojiValueToShortcodes)\n                              .map((c) => c.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, \"\\\\$&\"))\n                              .sort((a, b) => b.length - a.length) // Sort to get composed emojis first\n                              .join(\"|\")\n                        : /(?!)/,\n                    \"gu\"\n                ),\n            };\n        }\n    }\n}\n\nexport const PICKER_PROPS = [\n    \"PickerComponent?\",\n    \"close?\",\n    \"onClose?\",\n    \"onSelect\",\n    \"state?\",\n    \"storeScroll?\",\n    \"mobile?\",\n];\n\nexport class EmojiPicker extends Component {\n    static props = [...PICKER_PROPS, \"class?\", \"initialSearchTerm?\"];\n    static template = \"web.EmojiPicker\";\n\n    categories = null;\n    /** @type {Emoji[]|null} */\n    emojis = null;\n    shouldScrollElem = null;\n    lastSearchTerm;\n    keyboardNavigated = false;\n\n    setup() {\n        this.gridRef = useRef(\"emoji-grid\");\n        this.navbarRef = useRef(\"navbar\");\n        this.ui = useService(\"ui\");\n        this.isMobileOS = isMobileOS();\n        this.state = useState({\n            activeEmojiIndex: 0,\n            categoryId: null,\n            searchTerm: this.props.initialSearchTerm ?? \"\",\n            /** @type {Emoji|undefined} */\n            hoveredEmoji: undefined,\n        });\n        this.frequentEmojiService = useService(\"web.frequent.emoji\");\n        useAutofocus();\n        onWillStart(async () => {\n            const { categories, emojis } = await loadEmoji();\n            this.categories = categories;\n            this.emojis = emojis;\n            this.emojiByCodepoints = Object.fromEntries(\n                this.emojis.map((emoji) => [emoji.codepoints, emoji])\n            );\n            this.recentCategory = {\n                name: \"Frequently used\",\n                displayName: _t(\"Frequently used\"),\n                title: \"\ud83d\udd53\",\n                sortId: 0,\n            };\n            this.state.categoryId = this.recentEmojis.length\n                ? this.recentCategory.sortId\n                : this.categories[0].sortId;\n        });\n        onMounted(() => {\n            if (this.emojis.length === 0) {\n                return;\n            }\n            this.navbarResizeObserver = new ResizeObserver(() => this.adaptNavbar());\n            this.navbarResizeObserver.observe(this.navbarRef.el);\n            this.adaptNavbar();\n            this.highlightActiveCategory();\n            if (this.props.storeScroll) {\n                this.gridRef.el.scrollTop = this.props.storeScroll.get();\n            }\n            this.state.hoveredEmoji = this.activeEmoji;\n        });\n        onPatched(() => {\n            if (this.emojis.length === 0) {\n                return;\n            }\n            if (this.shouldScrollElem) {\n                this.shouldScrollElem = false;\n                const getElement = () =>\n                    this.gridRef.el.querySelector(\n                        `.o-EmojiPicker-category[data-category=\"${this.state.categoryId}\"`\n                    );\n                const elem = getElement();\n                if (elem) {\n                    elem.scrollIntoView();\n                } else {\n                    this.shouldScrollElem = getElement;\n                }\n            }\n        });\n        useEffect(\n            () => this.updateEmojiPickerRepr(),\n            () => [this.state.categoryId, this.state.searchTerm]\n        );\n        useEffect(\n            (el) => {\n                const gridEl = this.gridRef.el;\n                const activeEl = gridEl?.querySelector(\".o-Emoji.o-active\");\n                if (!gridEl) {\n                    return;\n                }\n                if (activeEl && this.keyboardNavigated && !isElementVisible(activeEl, gridEl)) {\n                    activeEl.scrollIntoView({ block: \"center\", behavior: \"instant\" });\n                    this.keyboardNavigated = false;\n                }\n                this.state.hoveredEmoji = this.activeEmoji;\n            },\n            () => [this.state.activeEmojiIndex, this.gridRef.el]\n        );\n        useEffect(\n            () => {\n                if (!this.gridRef.el) {\n                    return;\n                }\n                if (this.searchTerm) {\n                    this.gridRef.el.scrollTop = 0;\n                    this.state.categoryId = null;\n                } else {\n                    if (this.lastSearchTerm) {\n                        this.gridRef.el.scrollTop = 0;\n                    }\n                    this.highlightActiveCategory();\n                }\n                this.lastSearchTerm = this.searchTerm;\n            },\n            () => [this.searchTerm]\n        );\n        onWillUnmount(() => {\n            this.navbarResizeObserver?.disconnect();\n            if (!this.gridRef.el) {\n                return;\n            }\n            if (this.props.storeScroll) {\n                this.props.storeScroll.set(this.gridRef.el.scrollTop);\n            }\n        });\n    }\n\n    adaptNavbar() {\n        if (!this.navbarRef.el) {\n            return;\n        }\n        const computedStyle = getComputedStyle(this.navbarRef.el);\n        const availableWidth =\n            this.navbarRef.el.getBoundingClientRect().width -\n            parseInt(computedStyle.paddingLeft) -\n            parseInt(computedStyle.marginLeft) -\n            parseInt(computedStyle.paddingLeft) -\n            parseInt(computedStyle.marginLeft);\n        const itemWidth = this.navbarRef.el.querySelector(\".o-Emoji\").getBoundingClientRect().width;\n        const gapWidth = parseInt(computedStyle.gap);\n        const maxAvailableNavbarItemAmountAtOnce = Math.floor(\n            availableWidth / (itemWidth + gapWidth)\n        );\n        const repr = [];\n        let panel = [];\n        const allCategories = this.getAllCategories();\n        for (const category of allCategories) {\n            if (\n                panel.length === maxAvailableNavbarItemAmountAtOnce - 1 &&\n                category !== allCategories.at(-1)\n            ) {\n                panel.push(\"next\");\n                repr.push(panel);\n                panel = [];\n                panel.push(\"previous\");\n            }\n            panel.push(category.sortId);\n        }\n        if (panel.length > 0) {\n            if (repr.length > 0) {\n                panel.push(\n                    ...[...Array(maxAvailableNavbarItemAmountAtOnce - panel.length)].map(\n                        (_, idx) => \"empty_\" + idx\n                    )\n                );\n            }\n            repr.push(panel);\n        }\n        this.state.emojiNavbarRepr = repr;\n    }\n\n    get currentNavbarPanel() {\n        if (!this.state.emojiNavbarRepr) {\n            return this.getAllCategories().map((c) => c.sortId);\n        }\n        if (this.state.categoryId === null || Number.isNaN(this.state.categoryId)) {\n            return this.state.emojiNavbarRepr[0];\n        }\n        return this.state.emojiNavbarRepr.find((panel) => panel.includes(this.state.categoryId));\n    }\n\n    get searchTerm() {\n        return this.props.state ? this.props.state.searchTerm : this.state.searchTerm;\n    }\n\n    set searchTerm(value) {\n        if (this.props.state) {\n            this.props.state.searchTerm = value;\n        } else {\n            this.state.searchTerm = value;\n        }\n    }\n\n    get itemsNumber() {\n        return this.recentEmojis.length + this.getEmojis().length;\n    }\n\n    get recentEmojis() {\n        const recent = Object.entries(this.frequentEmojiService.all)\n            .sort(([, usage_1], [, usage_2]) => usage_2 - usage_1)\n            .map(([codepoints]) => this.emojiByCodepoints[codepoints]);\n        if (this.searchTerm && recent.length > 0) {\n            return fuzzyLookup(this.searchTerm, recent, (emoji) => [\n                emoji.name,\n                ...emoji.keywords,\n                ...emoji.emoticons,\n                ...emoji.shortcodes,\n            ]);\n        }\n        return recent.slice(0, 42);\n    }\n\n    get placeholder() {\n        return this.state.hoveredEmoji?.shortcodes.join(\" \") ?? _t(\"Search emoji\");\n    }\n\n    onMouseenterEmoji(ev, emoji) {\n        this.state.hoveredEmoji = emoji;\n    }\n\n    onMouseleaveEmoji(ev, emoji) {\n        this.state.hoveredEmoji = this.activeEmoji;\n    }\n\n    onClick(ev) {\n        markEventHandled(ev, \"emoji.selectEmoji\");\n    }\n\n    onClickToNextCategories() {\n        const panelIndex = this.state.emojiNavbarRepr.findIndex((p) =>\n            p.includes(this.state.categoryId)\n        );\n        this.selectCategory(this.state.emojiNavbarRepr[panelIndex + 1][1]);\n    }\n\n    onClickToPreviousCategories() {\n        const panelIndex = this.state.emojiNavbarRepr.findIndex((p) =>\n            p.includes(this.state.categoryId)\n        );\n        this.selectCategory(this.state.emojiNavbarRepr[panelIndex - 1].at(-2));\n    }\n\n    /**\n     * Builds the representation of the emoji picker (a 2D matrix of emojis)\n     * from the current DOM state. This is necessary to handle keyboard\n     * navigation of the emoji picker.\n     */\n    updateEmojiPickerRepr() {\n        if (this.emojis.length === 0) {\n            return;\n        }\n        const emojiEls = Array.from(this.gridRef.el.querySelectorAll(\".o-Emoji\"));\n        const emojiRects = emojiEls.map((el) => el.getBoundingClientRect());\n        this.emojiMatrix = [];\n        for (const [index, pos] of emojiRects.entries()) {\n            const emojiIndex = emojiEls[index].dataset.index;\n            if (this.emojiMatrix.length === 0 || pos.top > emojiRects[index - 1].top) {\n                this.emojiMatrix.push([]);\n            }\n            this.emojiMatrix.at(-1).push(parseInt(emojiIndex));\n        }\n    }\n\n    handleNavigation(key) {\n        const currentIdx = this.state.activeEmojiIndex;\n        let currentRow = -1;\n        let currentCol = -1;\n        const rowIdx = this.emojiMatrix.findIndex((row) => row.includes(currentIdx));\n        if (rowIdx !== -1) {\n            currentRow = rowIdx;\n            currentCol = this.emojiMatrix[currentRow].indexOf(currentIdx);\n        }\n        let newIdx;\n        switch (key) {\n            case \"ArrowDown\": {\n                const rowBelow = this.emojiMatrix[currentRow + 1];\n                const rowBelowBelow = this.emojiMatrix[currentRow + 2];\n                if (rowBelow?.length <= currentCol && rowBelowBelow?.length >= currentCol) {\n                    newIdx = rowBelowBelow?.[currentCol];\n                } else {\n                    newIdx = rowBelow?.[Math.min(currentCol, rowBelow.length - 1)];\n                }\n                break;\n            }\n            case \"ArrowUp\": {\n                const rowAbove = this.emojiMatrix[currentRow - 1];\n                const rowAboveAbove = this.emojiMatrix[currentRow - 2];\n                if (rowAbove?.length <= currentCol && rowAboveAbove?.length >= currentCol) {\n                    newIdx = rowAboveAbove?.[currentCol];\n                } else {\n                    newIdx = rowAbove?.[Math.min(currentCol, rowAbove.length - 1)];\n                }\n                break;\n            }\n            case \"ArrowRight\": {\n                const colRight = currentCol + 1;\n                if (colRight === this.emojiMatrix[currentRow]?.length) {\n                    const rowBelowRight = this.emojiMatrix[currentRow + 1];\n                    newIdx = rowBelowRight?.[0];\n                } else {\n                    newIdx = this.emojiMatrix[currentRow]?.[colRight];\n                }\n                break;\n            }\n            case \"ArrowLeft\": {\n                const colLeft = currentCol - 1;\n                if (colLeft < 0) {\n                    const rowAboveLeft = this.emojiMatrix[currentRow - 1];\n                    newIdx = rowAboveLeft?.[rowAboveLeft.length - 1] ?? this.state.activeEmojiIndex;\n                } else {\n                    newIdx = this.emojiMatrix[currentRow][colLeft];\n                }\n                break;\n            }\n        }\n        this.state.activeEmojiIndex = newIdx ?? this.state.activeEmojiIndex;\n    }\n\n    get activeEmoji() {\n        const activeCodepoints = this.gridRef.el.querySelector(\n            `.o-EmojiPicker-content .o-Emoji[data-index=\"${this.state.activeEmojiIndex}\"]`\n        )?.dataset.codepoints;\n        return activeCodepoints ? this.emojiByCodepoints[activeCodepoints] : undefined;\n    }\n\n    onKeydown(ev) {\n        switch (ev.key) {\n            case \"ArrowDown\":\n            case \"ArrowUp\":\n            case \"ArrowRight\":\n            case \"ArrowLeft\":\n                this.handleNavigation(ev.key);\n                this.keyboardNavigated = true;\n                break;\n            case \"Enter\":\n                ev.preventDefault();\n                this.gridRef.el\n                    ?.querySelector(\n                        `.o-EmojiPicker-content .o-Emoji[data-index=\"${this.state.activeEmojiIndex}\"]`\n                    )\n                    ?.click();\n                break;\n            case \"Escape\":\n                this.props.close?.();\n                this.props.onClose?.();\n                ev.stopPropagation();\n        }\n    }\n\n    getAllCategories() {\n        const res = [...this.categories];\n        if (this.recentEmojis.length > 0) {\n            res.unshift(this.recentCategory);\n        }\n        return res;\n    }\n\n    getEmojis() {\n        let emojisToDisplay = [...this.emojis];\n        const recentEmojis = this.recentEmojis;\n        if (recentEmojis.length > 0 && this.searchTerm) {\n            emojisToDisplay = emojisToDisplay.filter((emoji) => !recentEmojis.includes(emoji));\n        }\n        if (this.searchTerm.length > 0) {\n            return fuzzyLookup(this.searchTerm, emojisToDisplay, (emoji) => [\n                emoji.name,\n                ...emoji.keywords,\n                ...emoji.emoticons,\n                ...emoji.shortcodes,\n            ]);\n        }\n        return emojisToDisplay;\n    }\n\n    getEmojisFromSearch() {\n        return [...this.recentEmojis, ...this.getEmojis()];\n    }\n\n    selectCategory(categoryId) {\n        this.searchTerm = \"\";\n        this.state.categoryId = categoryId;\n        this.shouldScrollElem = true;\n    }\n\n    selectEmoji(ev) {\n        const codepoints = ev.currentTarget.dataset.codepoints;\n        let resetOnSelect = !ev.shiftKey;\n        const res = this.props.onSelect(codepoints, resetOnSelect);\n        if (res === false) {\n            resetOnSelect = false;\n        }\n        this.frequentEmojiService.incrementEmojiUsage(codepoints);\n        if (resetOnSelect) {\n            this.gridRef.el.scrollTop = 0;\n            this.props.close?.();\n            this.props.onClose?.();\n        }\n    }\n\n    highlightActiveCategory() {\n        if (!this.gridRef || !this.gridRef.el) {\n            return;\n        }\n        const coords = this.gridRef.el.getBoundingClientRect();\n        const res = document.elementFromPoint(coords.x + 10, coords.y + 10);\n        if (!res) {\n            return;\n        }\n        this.state.categoryId = parseInt(res.dataset.category);\n    }\n}\n\n/**\n * @param {() => {}} PickerComponent\n * @param {import(\"@web/core/utils/hooks\").Ref} [ref]\n * @param {Object} props\n * @param {import(\"@web/core/popover/popover_service\").PopoverServiceAddOptions} [options]\n * @param {function} [props.onSelect] function that is invoked when an item in picker has been selected.\n *   When explicit value `false` is returned, this will keep the picker open (= it won't auto-close it)\n * @param {function} [props.onClose]\n */\nexport function usePicker(PickerComponent, ref, props, options = {}) {\n    const component = useComponent();\n    const targets = [];\n    const state = useState({ isOpen: false });\n    const ui = useService(\"ui\");\n    const dialog = useService(\"dialog\");\n    let remove;\n    const newOptions = {\n        ...options,\n        onClose: () => {\n            state.isOpen = false;\n            options.onClose?.();\n        },\n    };\n    const popover = usePopover(PickerComponent, {\n        ...newOptions,\n        animation: false,\n        popoverClass: options.popoverClass ?? \"\" + \" bg-100 border border-secondary\",\n    });\n    props.storeScroll = {\n        scrollValue: 0,\n        set: (value) => {\n            props.storeScroll.scrollValue = value;\n        },\n        get: () => props.storeScroll.scrollValue,\n    };\n\n    /**\n     * @param {import(\"@web/core/utils/hooks\").Ref} ref\n     */\n    function add(ref, onSelect, { show = false } = {}) {\n        const toggler = () => toggle(isMobileOS() ? undefined : ref, onSelect);\n        targets.push([ref, toggler]);\n        if (!ref.el) {\n            return;\n        }\n        ref.el.addEventListener(\"click\", toggler);\n        ref.el.addEventListener(\"mouseenter\", loadEmoji);\n        if (show) {\n            ref.el.click();\n        }\n    }\n\n    function open(ref, openProps) {\n        state.isOpen = true;\n        if (ui.isSmall || isMobileOS()) {\n            const def = new Deferred();\n            const pickerMobileProps = {\n                PickerComponent,\n                onSelect: (...args) => {\n                    const func = openProps?.onSelect ?? props?.onSelect;\n                    const res = func?.(...args);\n                    def.resolve(true);\n                    return res;\n                },\n            };\n            if (ref?.el) {\n                pickerMobileProps.close = () => remove();\n                const app = new App(PickerMobile, {\n                    name: \"Popout\",\n                    env: component.env,\n                    props: pickerMobileProps,\n                    getTemplate,\n                    translatableAttributes: [\"data-tooltip\"],\n                    translateFn: appTranslateFn,\n                });\n                app.mount(ref.el);\n                remove = () => {\n                    state.isOpen = false;\n                    props.onClose?.();\n                    app.destroy();\n                };\n            } else {\n                remove = dialog.add(PickerMobileInDialog, pickerMobileProps, {\n                    context: component,\n                    onClose: () => {\n                        state.isOpen = false;\n                        return def.resolve(false);\n                    },\n                });\n            }\n            return def;\n        }\n        return popover.open(ref.el, { ...props, ...openProps });\n    }\n\n    function close() {\n        remove?.();\n        popover.close?.();\n    }\n\n    function toggle(ref, onSelect = props.onSelect) {\n        if (state.isOpen) {\n            close();\n        } else {\n            open(ref, { ...props, onSelect });\n        }\n    }\n\n    if (ref) {\n        add(ref);\n    }\n    onMounted(() => {\n        for (const [ref, toggle] of targets) {\n            if (!ref.el) {\n                continue;\n            }\n            ref.el.addEventListener(\"click\", toggle);\n            ref.el.addEventListener(\"mouseenter\", loadEmoji);\n        }\n    });\n    onWillPatch(() => {\n        for (const [ref, toggle] of targets) {\n            if (!ref.el) {\n                continue;\n            }\n            ref.el.removeEventListener(\"click\", toggle);\n            ref.el.removeEventListener(\"mouseenter\", loadEmoji);\n        }\n    });\n    onPatched(() => {\n        for (const [ref, toggle] of targets) {\n            if (!ref.el) {\n                continue;\n            }\n            ref.el.addEventListener(\"click\", toggle);\n            ref.el.addEventListener(\"mouseenter\", loadEmoji);\n        }\n    });\n    Object.assign(state, { open, close, toggle });\n    return state;\n}\n\nclass PickerMobile extends Component {\n    static props = [...PICKER_PROPS, \"onClose?\"];\n    static template = xml`\n        <t t-component=\"props.PickerComponent\" t-props=\"pickerProps\"/>\n    `;\n\n    get pickerProps() {\n        return {\n            ...this.props,\n            onSelect: (...args) => this.props.onSelect(...args),\n            mobile: true,\n        };\n    }\n}\n\nclass PickerMobileInDialog extends PickerMobile {\n    static components = { Dialog };\n    static props = [...PICKER_PROPS, \"onClose?\"];\n    static template = xml`\n        <Dialog size=\"'lg'\" header=\"false\" footer=\"false\" contentClass=\"'o-discuss-mobileContextMenu d-flex position-absolute bottom-0 rounded-0 h-50 bg-100'\" bodyClass=\"'p-1'\">\n            <div class=\"h-100\" t-ref=\"root\">\n                <t t-component=\"props.PickerComponent\" t-props=\"pickerProps\"/>\n            </div>\n        </Dialog>\n    `;\n\n    setup() {\n        super.setup();\n        this.root = useRef(\"root\");\n        useExternalListener(\n            window,\n            \"click\",\n            (ev) => {\n                if (ev.target !== this.root.el && !this.root.el.contains(ev.target)) {\n                    this.props.close?.();\n                }\n            },\n            { capture: true }\n        );\n    }\n}\n\nfunction isElementVisible(el, holder) {\n    const offset = 20;\n    holder = holder || document.body;\n    const { top, bottom, height } = el.getBoundingClientRect();\n    let { top: holderTop, bottom: holderBottom } = holder.getBoundingClientRect();\n    holderTop += offset * 2; // section are position sticky top so emoji can be \"visible\" under section name. Overestimate to assume invisible.\n    holderBottom -= offset;\n    return top - offset <= holderTop ? holderTop - top <= height : bottom - holderBottom <= height;\n}\n", "import { reactive } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\n\nexport const frequentEmojiService = {\n    start() {\n        const state = reactive({\n            all: JSON.parse(browser.localStorage.getItem(\"web.emoji.frequent\") || \"{}\"),\n            incrementEmojiUsage(codepoints) {\n                state.all[codepoints] ??= 0;\n                state.all[codepoints]++;\n                browser.localStorage.setItem(\"web.emoji.frequent\", JSON.stringify(state.all));\n            },\n            getMostFrequent(limit) {\n                return Object.entries(state.all)\n                    .sort(([, usage_1], [, usage_2]) => usage_2 - usage_1)\n                    .slice(0, limit ?? Infinity)\n                    .map(([codepoints]) => codepoints);\n            },\n        });\n        browser.addEventListener(\"storage\", (ev) => {\n            if (ev.key === \"web.emoji.frequent\") {\n                state.all = ev.newValue ? JSON.parse(ev.newValue) : {};\n            } else if (ev.key === null) {\n                state.all = {};\n            }\n        });\n        return state;\n    },\n};\n\nregistry.category(\"services\").add(\"web.frequent.emoji\", frequentEmojiService);\n", "import { loadBundle, loadJS } from \"./assets\";\n\nexport async function ensureJQuery() {\n    if (!window.jQuery) {\n        await loadBundle(\"web._assets_jquery\");\n        // allow to instantiate Bootstrap classes via jQuery: e.g. $(...).dropdown\n        const BTS_CLASSES = [\"Carousel\", \"Dropdown\", \"Modal\", \"Popover\", \"Tooltip\", \"Collapse\"];\n        const $ = window.jQuery;\n        for (const CLS of BTS_CLASSES) {\n            const plugin = window[CLS];\n            if (plugin) {\n                const name = plugin.NAME;\n                const JQUERY_NO_CONFLICT = $.fn[name];\n                $.fn[name] = plugin.jQueryInterface;\n                $.fn[name].Constructor = plugin;\n                $.fn[name].noConflict = () => {\n                    $.fn[name] = JQUERY_NO_CONFLICT;\n                    return plugin.jQueryInterface;\n                };\n            }\n        }\n    } else if (!window.jQuery.fn.getScrollingElement) {\n        await loadJS(\"/web/static/src/legacy/js/libs/jquery.js\");\n    }\n}\n", "import { browser } from \"../browser/browser\";\nimport { Dialog } from \"../dialog/dialog\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"../registry\";\nimport { Tooltip } from \"@web/core/tooltip/tooltip\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { capitalize } from \"../utils/strings\";\n\nimport { Component, useRef, useState, markup } from \"@odoo/owl\";\n\nconst { DateTime } = luxon;\n\n// This props are added by the error handler\nexport const standardErrorDialogProps = {\n    traceback: { type: [String, { value: null }], optional: true },\n    message: { type: String, optional: true },\n    name: { type: String, optional: true },\n    exceptionName: { type: [String, { value: null }], optional: true },\n    data: { type: [Object, { value: null }], optional: true },\n    subType: { type: [String, { value: null }], optional: true },\n    code: { type: [Number, String, { value: null }], optional: true },\n    type: { type: [String, { value: null }], optional: true },\n    serverHost: { type: [String, { value: null }], optional: true },\n    id: { type: [Number, { value: null }], optional: true },\n    model: { type: [String, { value: null }], optional: true },\n    close: Function, // prop added by the Dialog service\n};\n\nexport const odooExceptionTitleMap = new Map(\n    Object.entries({\n        \"odoo.addons.base.models.ir_mail_server.MailDeliveryException\": _t(\"MailDeliveryException\"),\n        \"odoo.exceptions.AccessDenied\": _t(\"Access Denied\"),\n        \"odoo.exceptions.MissingError\": _t(\"Missing Record\"),\n        \"odoo.addons.web.controllers.action.MissingActionError\": _t(\"Missing Action\"),\n        \"odoo.addons.base.models.ir_actions.ServerActionWithWarningsError\": _t(\"Invalid Operation\"),\n        \"odoo.exceptions.UserError\": _t(\"Invalid Operation\"),\n        \"odoo.exceptions.ValidationError\": _t(\"Validation Error\"),\n        \"odoo.exceptions.AccessError\": _t(\"Access Error\"),\n        \"odoo.exceptions.Warning\": _t(\"Warning\"),\n    })\n);\n\n// -----------------------------------------------------------------------------\n// Generic Error Dialog\n// -----------------------------------------------------------------------------\nexport class ErrorDialog extends Component {\n    static template = \"web.ErrorDialog\";\n    static components = { Dialog };\n    static title = _t(\"Odoo Error\");\n    static showTracebackButtonText = _t(\"See technical details\");\n    static hideTracebackButtonText = _t(\"Hide technical details\");\n    static props = { ...standardErrorDialogProps };\n\n    setup() {\n        this.state = useState({\n            showTraceback: false,\n        });\n        this.copyButtonRef = useRef(\"copyButton\");\n        this.popover = usePopover(Tooltip);\n        this.contextDetails = \"Occured \";\n        if (this.props.serverHost) {\n            this.contextDetails += `on ${this.props.serverHost} `;\n        }\n        if (this.props.model) {\n            this.contextDetails += `on model ${this.props.model} `;\n        }\n        this.contextDetails += `on ${DateTime.now()\n            .setZone(\"UTC\")\n            .toFormat(\"yyyy-MM-dd HH:mm:ss\")} GMT`;\n    }\n\n    showTooltip() {\n        this.popover.open(this.copyButtonRef.el, { tooltip: _t(\"Copied\") });\n        browser.setTimeout(this.popover.close, 800);\n    }\n\n    onClickClipboard() {\n        browser.navigator.clipboard.writeText(\n            `${this.props.name}\\n\\n${this.props.message}\\n\\n${this.contextDetails}\\n\\n${this.props.traceback}`\n        );\n        this.showTooltip();\n    }\n}\n\n// -----------------------------------------------------------------------------\n// Client Error Dialog\n// -----------------------------------------------------------------------------\nexport class ClientErrorDialog extends ErrorDialog {}\nClientErrorDialog.title = _t(\"Odoo Client Error\");\n\n// -----------------------------------------------------------------------------\n// Network Error Dialog\n// -----------------------------------------------------------------------------\nexport class NetworkErrorDialog extends ErrorDialog {}\nNetworkErrorDialog.title = _t(\"Odoo Network Error\");\n\n// -----------------------------------------------------------------------------\n// RPC Error Dialog\n// -----------------------------------------------------------------------------\nexport class RPCErrorDialog extends ErrorDialog {\n    setup() {\n        super.setup();\n        this.inferTitle();\n        this.traceback = this.props.traceback;\n        if (this.props.data && this.props.data.debug) {\n            this.traceback = `${this.props.data.debug}\\nThe above server error caused the following client error:\\n${this.traceback}`;\n        }\n    }\n    inferTitle() {\n        // If the server provides an exception name that we have in a registry.\n        if (this.props.exceptionName && odooExceptionTitleMap.has(this.props.exceptionName)) {\n            this.title = odooExceptionTitleMap.get(this.props.exceptionName).toString();\n            return;\n        }\n        // Fall back to a name based on the error type.\n        if (!this.props.type) {\n            return;\n        }\n        switch (this.props.type) {\n            case \"server\":\n                this.title = _t(\"Odoo Server Error\");\n                break;\n            case \"script\":\n                this.title = _t(\"Odoo Client Error\");\n                break;\n            case \"network\":\n                this.title = _t(\"Odoo Network Error\");\n                break;\n        }\n    }\n\n    onClickClipboard() {\n        browser.navigator.clipboard.writeText(\n            `${this.props.name}\\n\\n${this.props.message}\\n\\n${this.contextDetails}\\n\\n${this.traceback}`\n        );\n        this.showTooltip();\n    }\n}\n\n// -----------------------------------------------------------------------------\n// Warning Dialog\n// -----------------------------------------------------------------------------\nexport class WarningDialog extends Component {\n    static template = \"web.WarningDialog\";\n    static components = { Dialog };\n    static props = {\n        ...standardErrorDialogProps,\n        title: { type: String, optional: true },\n    };\n\n    setup() {\n        this.title = this.inferTitle();\n        const { data, message } = this.props;\n        if (data && data.arguments && data.arguments.length > 0) {\n            this.message = data.arguments[0];\n        } else {\n            this.message = message;\n        }\n    }\n    inferTitle() {\n        if (this.props.exceptionName && odooExceptionTitleMap.has(this.props.exceptionName)) {\n            return odooExceptionTitleMap.get(this.props.exceptionName).toString();\n        }\n        return this.props.title || _t(\"Odoo Warning\");\n    }\n}\n\n// -----------------------------------------------------------------------------\n// Redirect Warning Dialog\n// -----------------------------------------------------------------------------\nexport class RedirectWarningDialog extends Component {\n    static template = \"web.RedirectWarningDialog\";\n    static components = { Dialog };\n    static props = { ...standardErrorDialogProps };\n\n    setup() {\n        this.actionService = useService(\"action\");\n        const { data, subType } = this.props;\n        const [message, actionId, buttonText, additionalContext] = data.arguments;\n        this.title = capitalize(subType) || _t(\"Odoo Warning\");\n        this.message = message;\n        this.actionId = actionId;\n        this.buttonText = buttonText;\n        this.additionalContext = additionalContext;\n    }\n    async onClick() {\n        const options = { forceLeave: true };\n        if (this.additionalContext) {\n            options.additionalContext = this.additionalContext;\n        }\n        if (this.actionId.help) {\n            this.actionId.help = markup(this.actionId.help);\n        }\n        await this.actionService.doAction(this.actionId, options);\n        this.props.close();\n    }\n}\n\n// -----------------------------------------------------------------------------\n// Error 504 Dialog\n// -----------------------------------------------------------------------------\nexport class Error504Dialog extends Component {\n    static template = \"web.Error504Dialog\";\n    static components = { Dialog };\n    static props = { ...standardErrorDialogProps };\n}\n\n// -----------------------------------------------------------------------------\n// Expired Session Error Dialog\n// -----------------------------------------------------------------------------\nexport class SessionExpiredDialog extends Component {\n    static template = \"web.SessionExpiredDialog\";\n    static components = { Dialog };\n    static props = { ...standardErrorDialogProps };\n\n    onClick() {\n        browser.location.reload();\n    }\n}\n\nregistry\n    .category(\"error_dialogs\")\n    .add(\"odoo.exceptions.AccessDenied\", WarningDialog)\n    .add(\"odoo.exceptions.AccessError\", WarningDialog)\n    .add(\"odoo.exceptions.MissingError\", WarningDialog)\n    .add(\"odoo.addons.web.controllers.action.MissingActionError\", WarningDialog)\n    .add(\"odoo.addons.base.models.ir_actions.ServerActionWithWarningsError\", WarningDialog)\n    .add(\"odoo.exceptions.UserError\", WarningDialog)\n    .add(\"odoo.exceptions.ValidationError\", WarningDialog)\n    .add(\"odoo.exceptions.RedirectWarning\", RedirectWarningDialog)\n    .add(\"odoo.http.SessionExpiredException\", SessionExpiredDialog)\n    .add(\"werkzeug.exceptions.Forbidden\", SessionExpiredDialog)\n    .add(\"504\", Error504Dialog);\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { browser } from \"../browser/browser\";\nimport { ConnectionLostError, RPCError, rpc } from \"../network/rpc\";\nimport { registry } from \"../registry\";\nimport { session } from \"@web/session\";\nimport { user } from \"@web/core/user\";\nimport {\n    ClientErrorDialog,\n    ErrorDialog,\n    NetworkErrorDialog,\n    RPCErrorDialog,\n} from \"./error_dialogs\";\nimport { UncaughtClientError, ThirdPartyScriptError, UncaughtPromiseError } from \"./error_service\";\n\n/**\n * @typedef {import(\"../../env\").OdooEnv} OdooEnv\n * @typedef {import(\"./error_service\").UncaughtError} UncaughError\n */\n\nconst errorHandlerRegistry = registry.category(\"error_handlers\");\nconst errorDialogRegistry = registry.category(\"error_dialogs\");\nconst errorNotificationRegistry = registry.category(\"error_notifications\");\n\n// -----------------------------------------------------------------------------\n// RPC errors\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @param {Error} originalError\n * @returns {boolean}\n */\nexport function rpcErrorHandler(env, error, originalError) {\n    if (!(error instanceof UncaughtPromiseError)) {\n        return false;\n    }\n    if (originalError instanceof RPCError) {\n        // When an error comes from the server, it can have an exeption name.\n        // (or any string truly). It is used as key in the error dialog from\n        // server registry to know which dialog component to use.\n        // It's how a backend dev can easily map its error to another component.\n        // Note that for a client side exception, we don't use this registry\n        // as we can directly assign a value to `component`.\n        // error is here a RPCError\n        error.unhandledRejectionEvent.preventDefault();\n        const exceptionName = originalError.exceptionName;\n        let ErrorComponent = originalError.Component;\n        if (!ErrorComponent && exceptionName) {\n            if (errorNotificationRegistry.contains(exceptionName)) {\n                const notif = errorNotificationRegistry.get(exceptionName);\n                env.services.notification.add(notif.message || originalError.data.message, notif);\n                return true;\n            }\n            if (errorDialogRegistry.contains(exceptionName)) {\n                ErrorComponent = errorDialogRegistry.get(exceptionName);\n            }\n        }\n        if (!ErrorComponent && originalError.data.context) {\n            const exceptionClass = originalError.data.context.exception_class;\n            if (errorDialogRegistry.contains(exceptionClass)) {\n                ErrorComponent = errorDialogRegistry.get(exceptionClass);\n            }\n        }\n\n        env.services.dialog.add(ErrorComponent || RPCErrorDialog, {\n            traceback: error.traceback,\n            message: originalError.message,\n            name: originalError.name,\n            exceptionName: originalError.exceptionName,\n            data: originalError.data,\n            subType: originalError.subType,\n            code: originalError.code,\n            type: originalError.type,\n            serverHost: error.event?.target?.location.host,\n            model: originalError.model,\n        });\n        return true;\n    }\n}\n\nerrorHandlerRegistry.add(\"rpcErrorHandler\", rpcErrorHandler, { sequence: 97 });\n\n// -----------------------------------------------------------------------------\n// Lost connection errors\n// -----------------------------------------------------------------------------\n\nlet connectionLostNotifRemove = null;\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @param {Error} originalError\n * @returns {boolean}\n */\nexport function lostConnectionHandler(env, error, originalError) {\n    if (!(error instanceof UncaughtPromiseError)) {\n        return false;\n    }\n    if (originalError instanceof ConnectionLostError) {\n        if (connectionLostNotifRemove) {\n            // notification already displayed (can occur if there were several\n            // concurrent rpcs when the connection was lost)\n            return true;\n        }\n        connectionLostNotifRemove = env.services.notification.add(\n            _t(\"Connection lost. Trying to reconnect...\"),\n            { sticky: true }\n        );\n        let delay = 2000;\n        browser.setTimeout(function checkConnection() {\n            rpc(\"/web/webclient/version_info\", {})\n                .then(function () {\n                    if (connectionLostNotifRemove) {\n                        connectionLostNotifRemove();\n                        connectionLostNotifRemove = null;\n                    }\n                    env.services.notification.add(_t(\"Connection restored. You are back online.\"), {\n                        type: \"info\",\n                    });\n                })\n                .catch(() => {\n                    // exponential backoff, with some jitter\n                    delay = delay * 1.5 + 500 * Math.random();\n                    browser.setTimeout(checkConnection, delay);\n                });\n        }, delay);\n        return true;\n    }\n}\nerrorHandlerRegistry.add(\"lostConnectionHandler\", lostConnectionHandler, { sequence: 98 });\n\n// -----------------------------------------------------------------------------\n// Default handler\n// -----------------------------------------------------------------------------\n\nconst defaultDialogs = new Map([\n    [UncaughtClientError, ClientErrorDialog],\n    [UncaughtPromiseError, ClientErrorDialog],\n    [ThirdPartyScriptError, NetworkErrorDialog],\n]);\n\n/**\n * Handles the errors based on the very general error categories emitted by the\n * error service. Notice how we do not look at the original error at all.\n *\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @returns {boolean}\n */\nexport function defaultHandler(env, error) {\n    const DialogComponent = defaultDialogs.get(error.constructor) || ErrorDialog;\n    env.services.dialog.add(DialogComponent, {\n        traceback: error.traceback,\n        message: error.message,\n        name: error.name,\n        serverHost: error.event?.target?.location.host,\n    });\n    return true;\n}\nerrorHandlerRegistry.add(\"defaultHandler\", defaultHandler, { sequence: 100 });\n\n// -----------------------------------------------------------------------------\n// Frontend visitors errors\n// -----------------------------------------------------------------------------\n\n/**\n * We don't want to show tracebacks to non internal users. This handler swallows\n * all errors if we're not an internal user (except in debug or test mode).\n */\nexport function swallowAllVisitorErrors(env, error, originalError) {\n    if (!user.isInternalUser && !odoo.debug && !session.test_mode) {\n        return true;\n    }\n}\n\nif (user.isInternalUser === undefined) {\n    // Only warn about this while on the \"frontend\": the session info might\n    // apparently not be present in all Odoo screens at the moment... TODO ?\n    if (session.is_frontend) {\n        console.warn(\n            \"isInternalUser information is required for this handler to work. It must be available in the page.\"\n        );\n    }\n} else {\n    registry.category(\"error_handlers\").add(\"swallowAllVisitorErrors\", swallowAllVisitorErrors, { sequence: 0 });\n}\n", "import { browser } from \"../browser/browser\";\nimport { registry } from \"../registry\";\nimport { completeUncaughtError, getErrorTechnicalName } from \"./error_utils\";\nimport { isBrowserFirefox, isBrowserChrome } from \"@web/core/browser/feature_detection\";\n\nexport class HTMLElementLoadingError extends Error {\n    static message = \"Error loading an HTML Element\";\n    constructor(message = HTMLElementLoadingError.message, event) {\n        super(message);\n        this.event = event;\n    }\n}\n\n/**\n * Uncaught Errors have 4 properties:\n * - name: technical name of the error (UncaughtError, ...)\n * - message: short user visible description of the issue (\"Uncaught Cors Error\")\n * - traceback: long description, possibly technical of the issue (such as a traceback)\n * - originalError: the error that was actually being caught. Note that it is not\n *      necessarily an error (for ex, if some code does throw \"boom\")\n */\nexport class UncaughtError extends Error {\n    constructor(message) {\n        super(message);\n        this.name = getErrorTechnicalName(this);\n        this.traceback = null;\n    }\n}\n\nexport class UncaughtClientError extends UncaughtError {\n    constructor(message = \"Uncaught Javascript Error\") {\n        super(message);\n    }\n}\n\nexport class UncaughtPromiseError extends UncaughtError {\n    constructor(message = \"Uncaught Promise\") {\n        super(message);\n        this.unhandledRejectionEvent = null;\n    }\n}\n\nexport class ThirdPartyScriptError extends UncaughtError {\n    constructor(message = \"Third-Party Script Error\") {\n        super(message);\n    }\n}\n\nexport const errorService = {\n    start(env) {\n        function handleError(uncaughtError, retry = true) {\n            function shouldLogError() {\n                // Only log errors that are relevant business-wise, following the heuristics:\n                // Error.event and Error.traceback have been assigned\n                // in one of the two error event listeners below.\n                // If preventDefault was already executed on the event, don't log it.\n                return (\n                    uncaughtError.event &&\n                    !uncaughtError.event.defaultPrevented &&\n                    uncaughtError.traceback\n                );\n            }\n            let originalError = uncaughtError;\n            while (originalError instanceof Error && \"cause\" in originalError) {\n                originalError = originalError.cause;\n            }\n            for (const [name, handler] of registry.category(\"error_handlers\").getEntries()) {\n                try {\n                    if (handler(env, uncaughtError, originalError)) {\n                        break;\n                    }\n                } catch (e) {\n                    if (shouldLogError()) {\n                        uncaughtError.event.preventDefault();\n                        console.error(\n                            `@web/core/error_service: handler \"${name}\" failed with \"${\n                                e.cause || e\n                            }\" while trying to handle:\\n` + uncaughtError.traceback\n                        );\n                    }\n                    return;\n                }\n            }\n            if (shouldLogError()) {\n                // Log the full traceback instead of letting the browser log the incomplete one\n                uncaughtError.event.preventDefault();\n                console.error(uncaughtError.traceback);\n            }\n        }\n\n        browser.addEventListener(\"error\", async (ev) => {\n            const { colno, error, filename, lineno, message } = ev;\n            // We never want to display the following ResizeObserver error to the end-user. It\n            // simply indicates that the browser delayed notifications to the next frame to prevent\n            // infinite loop, which is how he's supposed to behave. However, it would be interesting\n            // to track places from where this error could be thrown, and try to fix them.\n            // https://trackjs.com/javascript-errors/resizeobserver-loop-completed-with-undelivered-notifications/\n            const resizeObserverError =\n                \"ResizeObserver loop completed with undelivered notifications.\";\n            if (!(error instanceof Error) && message === resizeObserverError) {\n                ev.preventDefault();\n                return;\n            }\n            const isRedactedError = !filename && !lineno && !colno;\n            const isThirdPartyScriptError =\n                isRedactedError ||\n                // Firefox doesn't hide details of errors occuring in third-party scripts, check origin explicitly\n                (isBrowserFirefox() && new URL(filename).origin !== window.location.origin);\n            // Don't display error dialogs for third party script errors unless we are in debug mode\n            if (isThirdPartyScriptError && !odoo.debug) {\n                return;\n            }\n            let uncaughtError;\n            if (isRedactedError) {\n                uncaughtError = new ThirdPartyScriptError();\n                uncaughtError.traceback =\n                    `An error whose details cannot be accessed by the Odoo framework has occurred.\\n` +\n                    `The error probably originates from a JavaScript file served from a different origin.\\n` +\n                    `The full error is available in the browser console.`;\n            } else {\n                uncaughtError = new UncaughtClientError();\n                uncaughtError.event = ev;\n                if (error instanceof Error) {\n                    error.errorEvent = ev;\n                    const annotated = env.debug && env.debug.includes(\"assets\");\n                    await completeUncaughtError(uncaughtError, error, annotated);\n                }\n            }\n            uncaughtError.cause = error;\n            handleError(uncaughtError);\n        });\n\n        browser.addEventListener(\"unhandledrejection\", async (ev) => {\n            let error = ev.reason;\n\n            if (error && error.type === \"error\" && \"eventPhase\" in error) {\n                // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/error_event\n                // See also MDN's img, script and iframe docs. The error Event *doesn't* bubble.\n                // We sometimes reject a promise with the Event dispatched by the \"error\" handler\n                // of an HTMLElement. If the code throwing that at us doesn't wrap the event in an\n                // actual Error, there is no reason to do more than the spec: we do not handle\n                // this error bubbling to us via the Promise being rejected.\n                if (!error.bubbles) {\n                    ev.preventDefault();\n                    return;\n                }\n                // If for some reason the error Event bubbles then do something\n                // a bit meaningful.\n                let message;\n                if (error.target) {\n                    message = `${HTMLElementLoadingError.message}: ${error.target.nodeName}`;\n                }\n                error = new HTMLElementLoadingError(message, error);\n            }\n\n            let traceback;\n            if (isBrowserChrome() && ev instanceof CustomEvent && error === undefined) {\n                // This fix is ad-hoc to a bug in the Honey Paypal extension\n                // They throw a CustomEvent instead of the specified PromiseRejectionEvent\n                // https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event\n                // Moreover Chrome doesn't seem to sandbox enough the extension, as it seems irrelevant\n                // to have extension's errors in the main business page.\n                // We want to ignore those errors as they are not produced by us, and are parasiting\n                // the navigation. We do this according to the heuristic expressed in the if.\n                if (!odoo.debug) {\n                    return;\n                }\n                traceback =\n                    `Uncaught unknown Error\\n` +\n                    `An unknown error occured. This may be due to a Chrome extension meddling with Odoo.\\n` +\n                    `(Opening your browser console might give you a hint on the error.)`;\n            }\n            const uncaughtError = new UncaughtPromiseError();\n            uncaughtError.unhandledRejectionEvent = ev;\n            uncaughtError.event = ev;\n            uncaughtError.traceback = traceback;\n            if (error instanceof Error) {\n                error.errorEvent = ev;\n                const annotated = env.debug && env.debug.includes(\"assets\");\n                await completeUncaughtError(uncaughtError, error, annotated);\n            }\n            uncaughtError.cause = error;\n            handleError(uncaughtError);\n        });\n    },\n};\n\nregistry.category(\"services\").add(\"error\", errorService, { sequence: 1 });\n", "import { loadJS } from \"../assets\"; // use the real, non patched (in tests), loadJS\n\n/** @typedef {import(\"./error_service\").UncaughtError} UncaughtError */\n\n/**\n * @param {UncaughtError} uncaughtError\n * @param {Error} originalError\n * @returns {string}\n */\nfunction combineErrorNames(uncaughtError, originalError) {\n    const originalErrorName = getErrorTechnicalName(originalError);\n    const uncaughtErrorName = getErrorTechnicalName(uncaughtError);\n    if (originalErrorName === Error.name) {\n        return uncaughtErrorName;\n    } else {\n        return `${uncaughtErrorName} > ${originalErrorName}`;\n    }\n}\n\n/**\n * Returns the full traceback for an error chain based on error causes\n *\n * @param {Error} error\n * @returns {string}\n */\nexport function fullTraceback(error) {\n    let traceback = formatTraceback(error);\n    let current = error.cause;\n    while (current) {\n        traceback += `\\n\\nCaused by: ${\n            current instanceof Error ? formatTraceback(current) : current\n        }`;\n        current = current.cause;\n    }\n    return traceback;\n}\n\n/**\n * Returns the full annotated traceback for an error chain based on error causes\n *\n * @param {Error} error\n * @returns {Promise<string>}\n */\nexport async function fullAnnotatedTraceback(error) {\n    if (error.annotatedTraceback) {\n        return error.annotatedTraceback;\n    }\n    // If we don't call preventDefault  synchronously while handling the error\n    // event, the error will be logged in the console with an unannotated\n    // traceback. This is a problem because annotating a traceback cannot be\n    // done synchronously. To work around this issue, we always call\n    // preventDefault, which means it is never logged but we rethrow the error\n    // after annotating its traceback, which will cause the error to be handled\n    // again after the traceback has been annotated, and this function will be\n    // called again and return synchronously (see above)\n    if (error.errorEvent) {\n        error.errorEvent.preventDefault();\n    }\n    let traceback;\n    try {\n        traceback = await annotateTraceback(error);\n        let current = error.cause;\n        while (current) {\n            traceback += `\\n\\nCaused by: ${\n                current instanceof Error ? await annotateTraceback(current) : current\n            }`;\n            current = current.cause;\n        }\n    } catch (e) {\n        console.warn(\"Failed to annotate traceback for error:\", error, \"failure reason:\", e);\n        traceback = fullTraceback(error);\n    }\n    error.annotatedTraceback = traceback;\n    if (error.errorEvent) {\n        throw error;\n    }\n    return traceback;\n}\n\n/**\n * @param {UncaughtError} uncaughtError\n * @param {Error} originalError\n * @param {boolean} annotated\n * @returns {Promise<void>}\n */\nexport async function completeUncaughtError(uncaughtError, originalError, annotated = false) {\n    uncaughtError.name = combineErrorNames(uncaughtError, originalError);\n    if (annotated) {\n        uncaughtError.traceback = await fullAnnotatedTraceback(originalError);\n    } else {\n        uncaughtError.traceback = fullTraceback(originalError);\n    }\n    if (originalError.message) {\n        uncaughtError.message = `${uncaughtError.message} > ${originalError.message}`;\n    }\n    uncaughtError.cause = originalError;\n}\n\n/**\n * @param {Error} error\n * @returns {string}\n */\nexport function getErrorTechnicalName(error) {\n    return error.name !== Error.name ? error.name : error.constructor.name;\n}\n\n/**\n * Format the traceback of an error. Basically, we just add the error message\n * in the traceback if necessary (Chrome already does it by default, but not\n * other browser.)\n *\n * @param {Error} error\n * @returns {string}\n */\nexport function formatTraceback(error) {\n    let traceback = error.stack;\n    const errorName = getErrorTechnicalName(error);\n    // ensure the proper error name and error message are present in the traceback, no matter the error.stack brower's formatting.\n    // Stack example:\n    // Error: Mock: Can't write value\n    //     _onOpenFormView@http://localhost:8069/web/content/425-baf33f1/web.assets.js:1064:30\n    //     ...\n    const descriptionLine = `${errorName}: ${error.message}`;\n    if (error.stack.split(\"\\n\")[0].trim() !== descriptionLine) {\n        // avoid having the description line twice if already present\n        traceback = `${descriptionLine}\\n${error.stack}`.replace(/\\n/g, \"\\n    \");\n    }\n    return traceback;\n}\n\n/**\n * Returns an annotated traceback from an error. This is asynchronous because\n * it needs to fetch the sourcemaps for each script involved in the error,\n * then compute the correct file/line numbers and add the information to the\n * correct line.\n *\n * @param {Error} error\n * @returns {Promise<string>}\n */\nexport async function annotateTraceback(error) {\n    const traceback = formatTraceback(error);\n    try {\n        await loadJS(\"/web/static/lib/stacktracejs/stacktrace.js\");\n    } catch {\n        return traceback;\n    }\n    // In Firefox, the error stack generated by anonymous code (example: invalid\n    // code in a template) is not compatible with the stacktrace lib. This code\n    // corrects the stack to make it compatible with the lib stacktrace.\n    if (error.stack) {\n        const regex = / line (\\d*) > (Function):(\\d*)/gm;\n        const subst = `:$1`;\n        error.stack = error.stack.replace(regex, subst);\n    }\n    // eslint-disable-next-line no-undef\n    let frames;\n    try {\n        frames = await StackTrace.fromError(error);\n    } catch (e) {\n        // This can crash if the originalError has no stack/stacktrace property\n        console.warn(\"The following error could not be annotated:\", error, e);\n        return traceback;\n    }\n    const lines = traceback.split(\"\\n\");\n    if (lines[lines.length - 1].trim() === \"\") {\n        // firefox traceback have an empty line at the end\n        lines.splice(-1);\n    }\n\n    let lineIndex = 0;\n    let frameIndex = 0;\n    while (frameIndex < frames.length) {\n        const line = lines[lineIndex];\n        // skip lines that have no location information as they don't correspond to a frame\n        if (!line.match(/:\\d+:\\d+\\)?$/)) {\n            lineIndex++;\n            continue;\n        }\n        const frame = frames[frameIndex];\n        const info = ` (${frame.fileName}:${frame.lineNumber})`;\n        lines[lineIndex] = line + info;\n        lineIndex++;\n        frameIndex++;\n    }\n    return lines.join(\"\\n\");\n}\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { _t, translationIsReady } from \"@web/core/l10n/translation\";\nimport { getOrigin } from \"@web/core/utils/urls\";\n\nconst scssErrorNotificationService = {\n    dependencies: [\"notification\"],\n    start(env, { notification }) {\n        const origin = getOrigin();\n        // Iframe with src \"about:blank\" origin isn't a valid base URL.\n        if (browser.location.origin === \"null\") {\n            return;\n        }\n        const assets = [...document.styleSheets].filter((sheet) => {\n            return (\n                sheet.href?.includes(\"/web\") &&\n                sheet.href?.includes(\"/assets/\") &&\n                // CORS security rules don't allow reading content in JS\n                new URL(sheet.href, browser.location.origin).origin === origin\n            );\n        });\n        translationIsReady.then(() => {\n            for (const asset of assets) {\n                let cssRules;\n                try {\n                    // The filter above isn't enough to protect against CORS errors when reading\n                    // the cssRules property. Indeed, it seems that if the protocol is http, reading\n                    // that property can also trigger a CORS error, even if the origin is the same.\n                    // Anyway, we never want this line to crash, so we protect it.\n                    // See opw 3746910.\n                    cssRules = asset.cssRules;\n                } catch {\n                    continue;\n                }\n                const lastRule = cssRules?.[cssRules?.length - 1];\n                if (lastRule?.selectorText === \"css_error_message\") {\n                    const message = _t(\n                        \"The style compilation failed. This is an administrator or developer error that must be fixed for the entire database before continuing working. See browser console or server logs for details.\"\n                    );\n                    notification.add(message, {\n                        title: _t(\"Style error\"),\n                        sticky: true,\n                        type: \"danger\",\n                    });\n                    console.log(\n                        lastRule.style.content\n                            .replaceAll(\"\\\\a\", \"\\n\")\n                            .replaceAll(\"\\\\*\", \"*\")\n                            .replaceAll(`\\\\\"`, `\"`)\n                    );\n                }\n            }\n        });\n    },\n};\nregistry.category(\"services\").add(\"scss_error_display\", scssErrorNotificationService);\n", "import { Component, onWillStart, onWillUpdateProps } from \"@odoo/owl\";\nimport { getExpressionDisplayedOperators } from \"@web/core/expression_editor/expression_editor_operator_editor\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { ModelFieldSelector } from \"@web/core/model_field_selector/model_field_selector\";\nimport { condition } from \"@web/core/tree_editor/condition_tree\";\nimport { expressionFromTree } from \"@web/core/tree_editor/expression_from_tree\";\nimport { TreeEditor } from \"@web/core/tree_editor/tree_editor\";\nimport { getOperatorEditorInfo } from \"@web/core/tree_editor/tree_editor_operator_editor\";\nimport { getDefaultValue } from \"@web/core/tree_editor/tree_editor_value_editors\";\nimport { treeFromExpression } from \"@web/core/tree_editor/tree_from_expression\";\nimport { getDefaultPath } from \"@web/core/tree_editor/utils\";\n\nexport class ExpressionEditor extends Component {\n    static template = \"web.ExpressionEditor\";\n    static components = { TreeEditor };\n    static props = {\n        resModel: String,\n        fields: Object,\n        expression: String,\n        update: Function,\n    };\n\n    setup() {\n        onWillStart(() => this.onPropsUpdated(this.props));\n        onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps));\n    }\n\n    async onPropsUpdated(props) {\n        this.filteredFields = Object.fromEntries(\n            Object.entries(props.fields).filter(([_, fieldDef]) => fieldDef.type !== \"properties\")\n        );\n        try {\n            this.tree = treeFromExpression(props.expression, {\n                getFieldDef: (name) => this.getFieldDef(name, props),\n                distributeNot: !this.isDebugMode,\n                generateSmartDates: false,\n            });\n        } catch {\n            this.tree = null;\n        }\n    }\n\n    getFieldDef(name, props = this.props) {\n        if (typeof name === \"string\") {\n            return props.fields[name] || null;\n        }\n        return null;\n    }\n\n    getDefaultCondition() {\n        const defaultPath = getDefaultPath(this.filteredFields);\n        const fieldDef = this.filteredFields[defaultPath];\n        const operator = getExpressionDisplayedOperators(fieldDef)[0];\n        const value = getDefaultValue(fieldDef, operator);\n        return condition(fieldDef.name, operator, value);\n    }\n\n    getDefaultOperator(fieldDef) {\n        return getExpressionDisplayedOperators(fieldDef)[0];\n    }\n\n    getOperatorEditorInfo(fieldDef) {\n        const operators = getExpressionDisplayedOperators(fieldDef);\n        return getOperatorEditorInfo(operators, fieldDef);\n    }\n\n    getPathEditorInfo(resModel, defaultCondition) {\n        if (resModel !== this.props.resModel) {\n            throw new Error(\n                `Expression editor doesn't support tree as value so resModel has to be props.resModel`\n            );\n        }\n        return {\n            component: ModelFieldSelector,\n            extractProps: ({ value, update }) => ({\n                path: value,\n                update,\n                resModel: this.props.resModel,\n                readonly: false,\n                filter: (fieldDef) => fieldDef.name in this.filteredFields,\n                showDebugInput: false,\n                followRelations: false,\n                isDebugMode: this.isDebugMode,\n            }),\n            isSupported: (value) => [0, 1].includes(value) || value in this.filteredFields,\n            // by construction, all values received by the path editor are O/1 or a field (name) in this.props.fields.\n            // (see _leafFromAST in condition_tree.js)\n            stringify: (value) => this.props.fields[value].string,\n            defaultValue: () => defaultCondition.path,\n            message: _t(\"Field properties not supported\"),\n        };\n    }\n\n    get isDebugMode() {\n        return !!this.env.debug;\n    }\n\n    onExpressionChange(expression) {\n        this.props.update(expression);\n    }\n\n    resetExpression() {\n        this.props.update(\"True\");\n    }\n\n    update(tree) {\n        const expression = expressionFromTree(tree, {\n            getFieldDef: (name) => this.getFieldDef(name),\n            generateSmartDates: false,\n        });\n        this.props.update(expression);\n    }\n}\n", "import { getDomainDisplayedOperators } from \"@web/core/domain_selector/domain_selector_operator_editor\";\n\nconst EXPRESSION_VALID_OPERATORS = [\n    \"<\",\n    \"<=\",\n    \">\",\n    \">=\",\n    \"between\",\n    \"in range\",\n    \"in\",\n    \"not in\",\n    \"=\",\n    \"!=\",\n    \"set\",\n    \"not set\",\n];\n\nexport function getExpressionDisplayedOperators(fieldDef) {\n    const operators = getDomainDisplayedOperators(fieldDef);\n    return operators.filter((operator) => EXPRESSION_VALID_OPERATORS.includes(operator));\n}\n", "import { Component, useRef, useState } from \"@odoo/owl\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { ExpressionEditor } from \"@web/core/expression_editor/expression_editor\";\nimport { evaluateExpr } from \"@web/core/py_js/py\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { user } from \"@web/core/user\";\n\nexport class ExpressionEditorDialog extends Component {\n    static components = { Dialog, ExpressionEditor };\n    static template = \"web.ExpressionEditorDialog\";\n    static props = {\n        close: Function,\n        resModel: String,\n        fields: Object,\n        expression: String,\n        onConfirm: Function,\n    };\n\n    setup() {\n        this.notification = useService(\"notification\");\n        this.state = useState({\n            expression: this.props.expression,\n        });\n        this.confirmButtonRef = useRef(\"confirm\");\n    }\n\n    get expressionEditorProps() {\n        return {\n            resModel: this.props.resModel,\n            fields: this.props.fields,\n            expression: this.state.expression,\n            update: (expression) => {\n                this.state.expression = expression;\n            },\n        };\n    }\n\n    makeDefaultRecord() {\n        const record = {};\n        for (const [name, { type }] of Object.entries(this.props.fields)) {\n            switch (type) {\n                case \"integer\":\n                case \"float\":\n                case \"monetary\":\n                    record[name] = name === \"id\" ? false : 0;\n                    break;\n                case \"one2many\":\n                case \"many2many\":\n                    record[name] = [];\n                    break;\n                default:\n                    record[name] = false;\n            }\n        }\n        return record;\n    }\n\n    async onConfirm() {\n        this.confirmButtonRef.el.disabled = true;\n        const record = this.makeDefaultRecord();\n        const evalContext = { ...user.context, ...record };\n        try {\n            evaluateExpr(this.state.expression, evalContext);\n        } catch {\n            if (this.confirmButtonRef.el) {\n                this.confirmButtonRef.el.disabled = false;\n            }\n            this.notification.add(_t(\"Expression is invalid. Please correct it\"), {\n                type: \"danger\",\n            });\n            return;\n        }\n        this.props.onConfirm(this.state.expression);\n        this.props.close();\n    }\n\n    onDiscard() {\n        this.props.close();\n    }\n}\n", "import { Domain } from \"@web/core/domain\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * @typedef {Object} LoadFieldsOptions\n * @property {string[]|false} [fieldNames]\n * @property {string[]} [attributes]\n */\n\nfunction getRelation(fieldDef, followRelationalProperties = false) {\n    if (fieldDef.relation) {\n        return fieldDef.relation;\n    }\n    if (fieldDef.comodel && followRelationalProperties) {\n        return fieldDef.comodel;\n    }\n    return null;\n}\n\nexport const fieldService = {\n    dependencies: [\"orm\"],\n    async: [\n        \"loadFieldInfo\",\n        \"loadFields\",\n        \"loadPath\",\n        \"loadPropertyDefinitions\",\n        \"loadPathDescription\",\n    ],\n    start(env, { orm }) {\n        /**\n         * @param {string} resModel\n         * @param {LoadFieldsOptions} [options]\n         * @returns {Promise<object>}\n         */\n        async function loadFields(resModel, options = {}) {\n            if (typeof resModel !== \"string\" || !resModel) {\n                throw new Error(`Invalid model name: ${resModel}`);\n            }\n            return orm\n                .cache({ type: \"disk\" })\n                .call(resModel, \"fields_get\", [options.fieldNames, options.attributes]);\n        }\n\n        /**\n         * @param {Object} fieldDefs\n         * @param {string} name\n         * @param {import(\"@web/core/domain\").DomainListRepr} [domain=[]]\n         * @returns {Promise<Object>}\n         */\n        async function _loadPropertyDefinitions(resModel, fieldDefs, name, domain = []) {\n            const {\n                definition_record: definitionRecord,\n                definition_record_field: definitionRecordField,\n            } = fieldDefs[name];\n            const definitionRecordModel = fieldDefs[definitionRecord].relation;\n\n            let result;\n            if (definitionRecordModel === \"properties.base.definition\") {\n                // Record without parent (eg `res.partner`)\n                result = await orm.call(\n                    \"properties.base.definition\",\n                    \"get_properties_base_definition\",\n                    [resModel, name]\n                );\n            } else {\n                // @ts-ignore\n                domain = Domain.and([[[definitionRecordField, \"!=\", false]], domain]).toList();\n                result = await orm.webSearchRead(definitionRecordModel, domain, {\n                    specification: {\n                        display_name: {},\n                        [definitionRecordField]: {},\n                    },\n                });\n            }\n\n            const definitions = {};\n            for (const record of result.records) {\n                for (const definition of record[definitionRecordField]) {\n                    definitions[definition.name] = {\n                        is_property: true,\n                        // for now, all properties are searchable but their definitions don't contain that info\n                        searchable: true,\n                        // differentiate definitions with same name but on different parent\n                        record_id: record.id,\n                        record_name: record.display_name,\n                        ...(definition.comodel ? { relation: definition.comodel } : {}),\n                        ...definition,\n                    };\n                }\n            }\n            return definitions;\n        }\n\n        /**\n         * @param {string} resModel\n         * @param {string} fieldName\n         * @param {import(\"@web/core/domain\").DomainListRepr} [domain]\n         * @returns {Promise<object[]>}\n         */\n        async function loadPropertyDefinitions(resModel, fieldName, domain) {\n            const fieldDefs = await loadFields(resModel);\n            return _loadPropertyDefinitions(resModel, fieldDefs, fieldName, domain);\n        }\n\n        /**\n         * @param {string|null} resModel valid model name or null (case virtual)\n         * @param {Object|null} fieldDefs\n         * @param {string[]} names\n         * @param {boolean} [followRelationalProperties=false]\n         */\n        async function _loadPath(resModel, fieldDefs, names, followRelationalProperties = false) {\n            if (!fieldDefs) {\n                return { isInvalid: \"path\", names, modelsInfo: [] };\n            }\n\n            const [name, ...remainingNames] = names;\n            const modelsInfo = [{ resModel, fieldDefs }];\n            if (resModel === \"*\" && remainingNames.length) {\n                return { isInvalid: \"path\", names, modelsInfo };\n            }\n\n            const fieldDef = fieldDefs[name];\n            if ((name !== \"*\" && !fieldDef) || (name === \"*\" && remainingNames.length)) {\n                return { isInvalid: \"path\", names, modelsInfo };\n            }\n\n            if (!remainingNames.length) {\n                return { names, modelsInfo };\n            }\n\n            let subResult;\n            const relation = getRelation(fieldDef, followRelationalProperties);\n            if (relation) {\n                subResult = await _loadPath(relation, await loadFields(relation), remainingNames);\n            } else if (fieldDef.type === \"properties\") {\n                subResult = await _loadPath(\n                    followRelationalProperties ? resModel : \"*\",\n                    await _loadPropertyDefinitions(resModel, fieldDefs, name),\n                    remainingNames\n                );\n            }\n\n            if (subResult) {\n                const result = {\n                    names,\n                    modelsInfo: [...modelsInfo, ...subResult.modelsInfo],\n                };\n                if (subResult.isInvalid) {\n                    result.isInvalid = \"path\";\n                }\n                return result;\n            }\n\n            return { isInvalid: \"path\", names, modelsInfo };\n        }\n\n        /**\n         * Note: the symbol * can be used at the end of path (e.g path=\"*\" or path=\"user_id.*\").\n         * It says to load the fields of the appropriate model.\n         * @param {string} resModel\n         * @param {string} path\n         * @returns {Promise<Object>}\n         */\n        async function loadPath(resModel, path = \"*\", followRelationalProperties = false) {\n            const fieldDefs = await loadFields(resModel);\n            if (typeof path !== \"string\" || !path) {\n                throw new Error(`Invalid path: ${path}`);\n            }\n            return _loadPath(resModel, fieldDefs, path.split(\".\"), followRelationalProperties);\n        }\n\n        /**\n         * @param {string} resModel\n         * @param {string} path\n         * @returns {Promise<Object>}\n         */\n        async function loadFieldInfo(resModel, path) {\n            if (typeof path !== \"string\" || !path || path === \"*\") {\n                return { resModel, fieldDef: null };\n            }\n            const { isInvalid, names, modelsInfo } = await loadPath(resModel, path);\n            if (isInvalid) {\n                return { resModel, fieldDef: null };\n            }\n            const name = names.at(-1);\n            const modelInfo = modelsInfo.at(-1);\n            return { resModel: modelInfo.resModel, fieldDef: modelInfo.fieldDefs[name] };\n        }\n\n        function makeString(value) {\n            return String(value ?? \"-\");\n        }\n\n        async function loadPathDescription(resModel, path, allowEmpty) {\n            if ([0, 1].includes(path)) {\n                return { isInvalid: false, displayNames: [makeString(path)] };\n            }\n            if (allowEmpty && !path) {\n                return { isInvalid: false, displayNames: [] };\n            }\n            if (typeof path !== \"string\" || !path || path === \"*\") {\n                return { isInvalid: true, displayNames: [makeString()] };\n            }\n            const { isInvalid, modelsInfo, names } = await loadPath(resModel, path);\n            const result = { isInvalid: !!isInvalid, displayNames: [] };\n            if (!isInvalid) {\n                const lastName = names.at(-1);\n                const lastFieldDef = modelsInfo.at(-1).fieldDefs[lastName];\n                if ([\"properties\", \"properties_definition\"].includes(lastFieldDef.type)) {\n                    // there is no known case where we want to select a 'properties' field directly\n                    result.isInvalid = true;\n                }\n            }\n            for (let index = 0; index < names.length; index++) {\n                const name = names[index];\n                const fieldDef = modelsInfo[index]?.fieldDefs[name];\n                result.displayNames.push(fieldDef?.string || makeString(name));\n            }\n            return result;\n        }\n\n        return {\n            loadFieldInfo,\n            loadFields,\n            loadPath,\n            loadPathDescription,\n            loadPropertyDefinitions,\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"field\", fieldService);\n", "import { Component, onMounted, useRef, useState } from \"@odoo/owl\";\nimport { useFileUploader } from \"@web/core/utils/files\";\n\n/**\n * Custom file input\n *\n * Component representing a customized input of type file. It takes a sub-template\n * in its default t-slot and uses it as the trigger to open the file upload\n * prompt.\n * @extends Component\n *\n * Props:\n * @param {string} [props.acceptedFileExtensions='*'] Comma-separated\n *      list of authorized file extensions (default to all).\n * @param {string} [props.route='/web/binary/upload'] Route called when\n *      a file is uploaded in the input.\n * @param {string} [props.resId]\n * @param {string} [props.resModel]\n * @param {string} [props.multiUpload=false] Whether the input should allow\n *      to upload multiple files at once.\n */\nexport class FileInput extends Component {\n    static template = \"web.FileInput\";\n    static defaultProps = {\n        acceptedFileExtensions: \"*\",\n        hidden: false,\n        multiUpload: false,\n        onUpload: () => {},\n        route: \"/web/binary/upload_attachment\",\n        beforeOpen: async () => true,\n    };\n    static props = {\n        acceptedFileExtensions: { type: String, optional: true },\n        autoOpen: { type: Boolean, optional: true },\n        hidden: { type: Boolean, optional: true },\n        multiUpload: { type: Boolean, optional: true },\n        onUpload: { type: Function, optional: true },\n        beforeOpen: { type: Function, optional: true },\n        resId: { type: Number, optional: true },\n        resModel: { type: String, optional: true },\n        route: { type: String, optional: true },\n        \"*\": true,\n    };\n\n    setup() {\n        this.uploadFiles = useFileUploader();\n        this.fileInputRef = useRef(\"file-input\");\n        this.state = useState({\n            // Disables upload button if currently uploading.\n            isDisable: false,\n        });\n\n        onMounted(() => {\n            if (this.props.autoOpen) {\n                this.onTriggerClicked();\n            }\n        });\n    }\n\n    get httpParams() {\n        const { resId, resModel } = this.props;\n        const params = {\n            csrf_token: odoo.csrf_token,\n            ufile: [...this.fileInputRef.el.files],\n        };\n        if (resModel) {\n            params.model = resModel;\n        }\n        if (resId !== undefined) {\n            params.id = resId;\n        }\n        return params;\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Upload an attachment to the given route with the given parameters:\n     * - ufile: list of files contained in the file input\n     * - csrf_token: CSRF token provided by the odoo global object\n     * - resModel: a specific model which will be given when creating the attachment\n     * - resId: the id of the resModel target instance\n     */\n    async onFileInputChange() {\n        this.state.isDisable = true;\n        const httpParams = this.httpParams;\n        if (this.props.onWillUploadFiles) {\n            try {\n                const files = await this.props.onWillUploadFiles(httpParams.ufile);\n                httpParams.ufile = files;\n            } catch (e) {\n                this.state.isDisable = false;\n                throw e;\n            }\n        }\n        const parsedFileData = await this.uploadFiles(this.props.route, httpParams);\n        if (parsedFileData) {\n            // When calling onUpload, also pass the files to allow to get data like their names\n            this.props.onUpload(\n                parsedFileData,\n                this.fileInputRef.el ? this.fileInputRef.el.files : []\n            );\n            // Because the input would not trigger this method if the same file name is uploaded,\n            // we must clear the value after handling the upload\n            this.fileInputRef.el.value = null;\n        }\n        this.state.isDisable = false;\n    }\n\n    /**\n     * Redirect clicks from the trigger element to the input.\n     */\n    async onTriggerClicked() {\n        if (await this.props.beforeOpen()) {\n            this.fileInputRef.el.click();\n        }\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { useService } from \"../utils/hooks\";\nimport { ConfirmationDialog } from \"../confirmation_dialog/confirmation_dialog\";\n\nimport { Component } from \"@odoo/owl\";\n\nexport class FileUploadProgressBar extends Component {\n    static template = \"web.FileUploadProgressBar\";\n    static props = {\n        fileUpload: { type: Object },\n    };\n\n    setup() {\n        this.dialogService = useService(\"dialog\");\n    }\n\n    onCancel() {\n        if (!this.props.fileUpload.xhr) {\n            return;\n        }\n        this.dialogService.add(ConfirmationDialog, {\n            body: _t(\"Do you really want to cancel the upload of %s?\", this.props.fileUpload.title),\n            confirm: () => {\n                this.props.fileUpload.xhr.abort();\n            },\n        });\n    }\n}\n", "import { Component } from \"@odoo/owl\";\n\nexport class FileUploadProgressContainer extends Component {\n    static template = \"web.FileUploadProgressContainer\";\n    static props = {\n        Component: { optional: false },\n        shouldDisplay: { type: Function, optional: true },\n        fileUploads: { type: Object },\n    };\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { FileUploadProgressBar } from \"./file_upload_progress_bar\";\n\nimport { Component } from \"@odoo/owl\";\n\nexport class FileUploadProgressRecord extends Component {\n    static template = \"\";\n    static components = {\n        FileUploadProgressBar,\n    };\n    static props = {\n        fileUpload: Object,\n        selector: { type: String, optional: true },\n    };\n    getProgressTexts() {\n        const fileUpload = this.props.fileUpload;\n        const percent = Math.round(fileUpload.progress * 100);\n        if (percent === 100) {\n            return {\n                left: _t(\"Processing...\"),\n                right: \"\",\n            };\n        } else {\n            const mbLoaded = Math.round(fileUpload.loaded / 1000000);\n            const mbTotal = Math.round(fileUpload.total / 1000000);\n            return {\n                left: _t(\"Uploading... (%s%)\", percent),\n                right: _t(\"(%(mbLoaded)s/%(mbTotal)sMB)\", { mbLoaded, mbTotal }),\n            };\n        }\n    }\n}\n\nexport class FileUploadProgressKanbanRecord extends FileUploadProgressRecord {\n    static template = \"web.FileUploadProgressKanbanRecord\";\n}\n\nexport class FileUploadProgressDataRow extends FileUploadProgressRecord {\n    static template = \"web.FileUploadProgressDataRow\";\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"../registry\";\n\nimport { EventBus, reactive } from \"@odoo/owl\";\n\nexport const fileUploadService = {\n    dependencies: [\"notification\"],\n    /**\n     * Overridden during tests to return a mocked XHR.\n     *\n     * @private\n     * @returns {XMLHttpRequest}\n     */\n    createXhr() {\n        return new window.XMLHttpRequest();\n    },\n\n    start(env, { notificationService }) {\n        const uploads = reactive({});\n        let nextId = 1;\n        const bus = new EventBus();\n\n        /**\n         * @param {string}                          route\n         * @param {FileList|Array<File>}            files\n         * @param {Object}                          [params]\n         * @param {function(formData): void}        [params.buildFormData]\n         * @param {Boolean}                         [params.displayErrorNotification]\n         * @returns {reactive}                      upload\n         * @returns {XMLHttpRequest}                upload.xhr\n         * @returns {FormData}                      upload.data\n         * @returns {Number}                        upload.progress\n         * @returns {Number}                        upload.loaded\n         * @returns {Number}                        upload.total\n         * @returns {String}                        upload.title\n         * @returns {String||undefined}             upload.type\n         */\n        const upload = async (route, files, params = {}) => {\n            const xhr = this.createXhr();\n            xhr.open(\"POST\", route);\n            const formData = new FormData();\n            formData.append(\"csrf_token\", odoo.csrf_token);\n            for (const file of files) {\n                formData.append(\"ufile\", file);\n            }\n            if (params.buildFormData) {\n                params.buildFormData(formData);\n            }\n            const upload = reactive({\n                id: nextId++,\n                xhr,\n                data: formData,\n                progress: 0,\n                loaded: 0,\n                total: 0,\n                state: \"pending\",\n                title: files.length === 1 ? files[0].name : _t(\"%s Files\", files.length),\n                type: files.length === 1 ? files[0].type : undefined,\n            });\n            uploads[upload.id] = upload;\n            // Progress listener\n            xhr.upload.addEventListener(\"progress\", async (ev) => {\n                upload.progress = ev.loaded / ev.total;\n                upload.loaded = ev.loaded;\n                upload.total = ev.total;\n                upload.state = \"loading\";\n            });\n            // Load listener\n            xhr.addEventListener(\"load\", () => {\n                delete uploads[upload.id];\n                upload.state = \"loaded\";\n                bus.trigger(\"FILE_UPLOAD_LOADED\", { upload });\n            });\n            // Error listener\n            xhr.addEventListener(\"error\", async () => {\n                delete uploads[upload.id];\n                upload.state = \"error\";\n                // Disable this option if you need more explicit error handling.\n                if (\n                    params.displayErrorNotification !== undefined &&\n                    params.displayErrorNotification\n                ) {\n                    notificationService.add(_t(\"An error occured while uploading.\"), {\n                        type: \"danger\",\n                        sticky: true,\n                    });\n                }\n                bus.trigger(\"FILE_UPLOAD_ERROR\", { upload });\n            });\n            // Abort listener, considered as error\n            xhr.addEventListener(\"abort\", async () => {\n                delete uploads[upload.id];\n                upload.state = \"abort\";\n                bus.trigger(\"FILE_UPLOAD_ERROR\", { upload });\n            });\n            xhr.send(formData);\n            bus.trigger(\"FILE_UPLOAD_ADDED\", { upload });\n            return upload;\n        };\n\n        return { bus, upload, uploads };\n    },\n};\n\nregistry.category(\"services\").add(\"file_upload\", fileUploadService);\n", "import { url } from \"@web/core/utils/urls\";\n\nexport const FileModelMixin = (T) =>\n    class extends T {\n        access_token;\n        checksum;\n        extension;\n        id;\n        mimetype;\n        name;\n        /** @type {string} */\n        ownership_token;\n        /** @type {string} */\n        raw_access_token;\n        /** @type {\"binary\"|\"url\"} */\n        type;\n        /** @type {string} */\n        tmpUrl;\n        /**\n         * This URL should not be used as the URL to serve the file. `urlRoute` should be used\n         * instead. The server will properly redirect to the correct URL when necessary.\n         *\n         * @type {string}\n         */\n        url;\n        /** @type {boolean} */\n        uploading;\n\n        get defaultSource() {\n            const route = url(this.urlRoute, this.urlQueryParams);\n            const encodedRoute = encodeURIComponent(route);\n            if (this.isPdf) {\n                return `/web/static/lib/pdfjs/web/viewer.html?file=${encodedRoute}#pagemode=none`;\n            }\n            if (this.isUrlYoutube) {\n                const urlArr = this.url.split(\"/\");\n                let token = urlArr[urlArr.length - 1];\n                if (token.includes(\"watch\")) {\n                    token = token.split(\"v=\")[1];\n                    const amp = token.indexOf(\"&\");\n                    if (amp !== -1) {\n                        token = token.substring(0, amp);\n                    }\n                }\n                return `https://www.youtube.com/embed/${token}`;\n            }\n            return route;\n        }\n\n        get downloadUrl() {\n            return url(this.urlRoute, { ...this.urlQueryParams, download: true });\n        }\n\n        get isImage() {\n            const imageMimetypes = [\n                \"image/bmp\",\n                \"image/gif\",\n                \"image/jpeg\",\n                \"image/png\",\n                \"image/svg+xml\",\n                \"image/tiff\",\n                \"image/x-icon\",\n                \"image/webp\",\n            ];\n            return imageMimetypes.includes(this.mimetype);\n        }\n\n        get isPdf() {\n            return this.mimetype && this.mimetype.startsWith(\"application/pdf\");\n        }\n\n        get isText() {\n            const textMimeType = [\n                \"application/javascript\",\n                \"application/json\",\n                \"text/css\",\n                \"text/html\",\n                \"text/plain\",\n            ];\n            return textMimeType.includes(this.mimetype);\n        }\n\n        get isUrl() {\n            return this.type === \"url\" && this.url;\n        }\n\n        get isUrlYoutube() {\n            return !!this.url && this.url.includes(\"youtu\");\n        }\n\n        get isVideo() {\n            const videoMimeTypes = [\"audio/mpeg\", \"video/x-matroska\", \"video/mp4\", \"video/webm\"];\n            return videoMimeTypes.includes(this.mimetype);\n        }\n\n        get isViewable() {\n            return (\n                (this.isText || this.isImage || this.isVideo || this.isPdf || this.isUrlYoutube) &&\n                !this.uploading\n            );\n        }\n\n        /**\n         * @returns {Object}\n         */\n        get urlQueryParams() {\n            if (this.uploading && this.tmpUrl) {\n                return {};\n            }\n            const params = {\n                access_token: this.raw_access_token || this.access_token,\n                filename: this.name,\n                unique: this.checksum,\n            };\n            for (const prop in params) {\n                if (!params[prop]) {\n                    delete params[prop];\n                }\n            }\n            return params;\n        }\n\n        /**\n         * @returns {string}\n         */\n        get urlRoute() {\n            if (this.uploading && this.tmpUrl) {\n                return this.tmpUrl;\n            }\n            return this.isImage ? `/web/image/${this.id}` : `/web/content/${this.id}`;\n        }\n    };\n\nexport class FileModel extends FileModelMixin(Object) {}\n", "import { Component, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { useAutofocus, useService } from \"@web/core/utils/hooks\";\nimport { hidePDFJSButtons } from \"@web/core/utils/pdfjs\";\n\n/**\n * @typedef {Object} File\n * @property {string} name\n * @property {string} downloadUrl\n * @property {boolean} [isImage]\n * @property {boolean} [isPdf]\n * @property {boolean} [isVideo]\n * @property {boolean} [isText]\n * @property {string} [defaultSource]\n * @property {boolean} [isUrlYoutube]\n * @property {string} [mimetype]\n * @property {boolean} [isViewable]\n * @typedef {Object} Props\n * @property {Array<File>} files\n * @property {number} startIndex\n * @property {function} close\n * @property {boolean} [modal]\n * @extends {Component<Props, Env>}\n */\nexport class FileViewer extends Component {\n    static template = \"web.FileViewer\";\n    static components = {};\n    static props = [\"files\", \"startIndex\", \"close?\", \"modal?\"];\n    static defaultProps = {\n        modal: true,\n    };\n\n    setup() {\n        useAutofocus();\n        this.imageRef = useRef(\"image\");\n        this.zoomerRef = useRef(\"zoomer\");\n        this.iframeViewerPdfRef = useRef(\"iframeViewerPdf\");\n\n        this.isDragging = false;\n        this.dragStartX = 0;\n        this.dragStartY = 0;\n\n        this.scrollZoomStep = 0.1;\n        this.zoomStep = 0.5;\n        this.minScale = 0.5;\n        this.translate = {\n            dx: 0,\n            dy: 0,\n            x: 0,\n            y: 0,\n        };\n\n        this.state = useState({\n            index: this.props.startIndex,\n            file: this.props.files[this.props.startIndex],\n            imageLoaded: false,\n            scale: 1,\n            angle: 0,\n        });\n        this.ui = useService(\"ui\");\n        useEffect(\n            (el) => {\n                if (el) {\n                    hidePDFJSButtons(this.iframeViewerPdfRef.el, {\n                        hideDownload: true,\n                        hidePrint: true,\n                    });\n                }\n            },\n            () => [this.iframeViewerPdfRef.el]\n        );\n    }\n\n    onImageLoaded() {\n        this.state.imageLoaded = true;\n    }\n\n    close() {\n        this.props.close && this.props.close();\n    }\n\n    next() {\n        const last = this.props.files.length - 1;\n        this.activateFile(this.state.index === last ? 0 : this.state.index + 1);\n    }\n\n    previous() {\n        const last = this.props.files.length - 1;\n        this.activateFile(this.state.index === 0 ? last : this.state.index - 1);\n    }\n\n    activateFile(index) {\n        this.state.index = index;\n        this.state.file = this.props.files[index];\n    }\n\n    onKeydown(ev) {\n        switch (ev.key) {\n            case \"ArrowRight\":\n                this.next();\n                break;\n            case \"ArrowLeft\":\n                this.previous();\n                break;\n            case \"Escape\":\n                this.close();\n                break;\n            case \"q\":\n                this.close();\n                break;\n        }\n        if (this.state.file.isImage) {\n            switch (ev.key) {\n                case \"r\":\n                    this.rotate();\n                    break;\n                case \"+\":\n                    this.zoomIn();\n                    break;\n                case \"-\":\n                    this.zoomOut();\n                    break;\n                case \"0\":\n                    this.resetZoom();\n                    break;\n            }\n        }\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    onWheelImage(ev) {\n        if (ev.deltaY > 0) {\n            this.zoomOut({ scroll: true });\n        } else {\n            this.zoomIn({ scroll: true });\n        }\n    }\n\n    /**\n     * @param {DragEvent} ev\n     */\n    onMousedownImage(ev) {\n        if (this.isDragging) {\n            return;\n        }\n        if (ev.button !== 0) {\n            return;\n        }\n        this.isDragging = true;\n        this.dragStartX = ev.clientX;\n        this.dragStartY = ev.clientY;\n    }\n\n    onMouseupImage() {\n        if (!this.isDragging) {\n            return;\n        }\n        this.isDragging = false;\n        this.translate.x += this.translate.dx;\n        this.translate.y += this.translate.dy;\n        this.translate.dx = 0;\n        this.translate.dy = 0;\n        this.updateZoomerStyle();\n    }\n\n    /**\n     * @param {DragEvent}\n     */\n    onMousemoveView(ev) {\n        if (!this.isDragging) {\n            return;\n        }\n        this.translate.dx = ev.clientX - this.dragStartX;\n        this.translate.dy = ev.clientY - this.dragStartY;\n        this.updateZoomerStyle();\n    }\n\n    resetZoom() {\n        this.state.scale = 1;\n        this.updateZoomerStyle();\n    }\n\n    rotate() {\n        this.state.angle += 90;\n    }\n\n    /**\n     * @param {{ scroll?: boolean }}\n     */\n    zoomIn({ scroll = false } = {}) {\n        this.state.scale = this.state.scale + (scroll ? this.scrollZoomStep : this.zoomStep);\n        this.updateZoomerStyle();\n    }\n\n    /**\n     * @param {{ scroll?: boolean }}\n     */\n    zoomOut({ scroll = false } = {}) {\n        if (this.state.scale === this.minScale) {\n            return;\n        }\n        const unflooredAdaptedScale =\n            this.state.scale - (scroll ? this.scrollZoomStep : this.zoomStep);\n        this.state.scale = Math.max(this.minScale, unflooredAdaptedScale);\n        this.updateZoomerStyle();\n    }\n\n    updateZoomerStyle() {\n        const tx =\n            this.imageRef.el.offsetWidth * this.state.scale > this.zoomerRef.el.offsetWidth\n                ? this.translate.x + this.translate.dx\n                : 0;\n        const ty =\n            this.imageRef.el.offsetHeight * this.state.scale > this.zoomerRef.el.offsetHeight\n                ? this.translate.y + this.translate.dy\n                : 0;\n        if (tx === 0) {\n            this.translate.x = 0;\n        }\n        if (ty === 0) {\n            this.translate.y = 0;\n        }\n        this.zoomerRef.el.style = \"transform: \" + `translate(${tx}px, ${ty}px)`;\n    }\n\n    get imageStyle() {\n        let style =\n            \"transform: \" +\n            `scale3d(${this.state.scale}, ${this.state.scale}, 1) ` +\n            `rotate(${this.state.angle}deg);`;\n\n        if (this.state.angle % 180 !== 0) {\n            style += `max-height: ${window.innerWidth}px; max-width: ${window.innerHeight}px;`;\n        } else {\n            style += \"max-height: 100%; max-width: 100%;\";\n        }\n        style += `background: repeating-conic-gradient(#ccc 0deg 90deg, #fff 90deg 180deg) 50% / 20px 20px;`;\n        return style;\n    }\n\n    onClickPrint() {\n        const printWindow = window.open(\"about:blank\", \"_new\");\n        printWindow.document.open();\n        printWindow.document.write(`\n                <html>\n                    <head>\n                        <script>\n                            function onloadImage() {\n                                setTimeout('printImage()', 10);\n                            }\n                            function printImage() {\n                                window.print();\n                                window.close();\n                            }\n                        </script>\n                    </head>\n                    <body onload='onloadImage()'>\n                        <img src=\"${this.state.file.defaultSource}\" alt=\"\"/>\n                    </body>\n                </html>`);\n        printWindow.document.close();\n    }\n}\n", "import { onWillDestroy } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\nimport { FileViewer } from \"./file_viewer\";\n\nlet id = 1;\n\nexport function createFileViewer() {\n    const fileViewerId = `web.file_viewer${id++}`;\n    /**\n     * @param {import(\"@web/core/file_viewer/file_viewer\").FileViewer.props.files[]} file\n     * @param {import(\"@web/core/file_viewer/file_viewer\").FileViewer.props.files} files\n     */\n    function open(file, files = [file]) {\n        close();\n        if (!file.isViewable) {\n            return;\n        }\n        if (files.length > 0) {\n            const viewableFiles = files.filter((file) => file.isViewable);\n            const index = viewableFiles.indexOf(file);\n            registry.category(\"main_components\").add(fileViewerId, {\n                Component: FileViewer,\n                props: { files: viewableFiles, startIndex: index, close },\n            });\n        }\n    }\n\n    function close() {\n        registry.category(\"main_components\").remove(fileViewerId);\n    }\n    return { open, close };\n}\n\nexport function useFileViewer() {\n    const { open, close } = createFileViewer();\n    onWillDestroy(close);\n    return { open, close };\n}\n", "import { useService } from \"@web/core/utils/hooks\";\n\nimport { useEffect } from \"@odoo/owl\";\n\n/**\n * This hook will register/unregister the given registration\n * when the caller component will mount/unmount.\n *\n * @param {string} hotkey\n * @param {import(\"./hotkey_service\").HotkeyCallback} callback\n * @param {import(\"./hotkey_service\").HotkeyOptions} [options] additional options\n */\nexport function useHotkey(hotkey, callback, options = {}) {\n    const hotkeyService = useService(\"hotkey\");\n    useEffect(\n        () => hotkeyService.add(hotkey, callback, options),\n        () => []\n    );\n}\n", "import { isMacOS } from \"../browser/feature_detection\";\nimport { registry } from \"../registry\";\nimport { browser } from \"../browser/browser\";\nimport { getVisibleElements } from \"../utils/ui\";\n\n/**\n * @typedef {(context: { area: HTMLElement, target: EventTarget }) => void} HotkeyCallback\n *\n * @typedef {Object} HotkeyOptions\n * @property {boolean} [allowRepeat]\n *  allow registration to perform multiple times when hotkey is held down\n * @property {boolean} [bypassEditableProtection]\n *  if true the hotkey service will call this registration\n *  even if an editable element is focused\n * @property {boolean} [global]\n *  allow registration to perform no matter the UI active element\n * @property {() => HTMLElement} [area]\n *  adds a restricted operating area for this hotkey\n * @property {(target: HTMLElement) => boolean} [isAvailable]\n *  adds a validation before calling the hotkey registration's callback\n * @property {() => HTMLElement} [withOverlay]\n *  provides the element on which the overlay should be displayed\n *  Please note that if provided the hotkey will only work with\n *  the overlay access key, similarly to all [data-hotkey] DOM attributes.\n *\n * @typedef {HotkeyOptions & {\n *  hotkey: string,\n *  callback: HotkeyCallback,\n *  activeElement: HTMLElement,\n * }} HotkeyRegistration\n */\n\nconst ALPHANUM_KEYS = \"abcdefghijklmnopqrstuvwxyz0123456789\".split(\"\");\nconst NAV_KEYS = [\n    \"arrowleft\",\n    \"arrowright\",\n    \"arrowup\",\n    \"arrowdown\",\n    \"pageup\",\n    \"pagedown\",\n    \"home\",\n    \"end\",\n    \"backspace\",\n    \"enter\",\n    \"tab\",\n    \"delete\",\n    \"space\",\n];\nconst MODIFIERS = [\"alt\", \"control\", \"shift\"];\nconst AUTHORIZED_KEYS = [...ALPHANUM_KEYS, ...NAV_KEYS, \"escape\", \"<\", \">\"];\n\n/**\n * Get the actual hotkey being pressed.\n *\n * @param {KeyboardEvent} ev\n * @returns {string} the active hotkey, in lowercase\n */\nexport function getActiveHotkey(ev) {\n    if (!ev.key) {\n        // Chrome may trigger incomplete keydown events under certain circumstances.\n        // E.g. when using browser built-in autocomplete on an input.\n        // See https://stackoverflow.com/questions/59534586/google-chrome-fires-keydown-event-when-form-autocomplete\n        return \"\";\n    }\n    if (ev.isComposing) {\n        // This case happens with an IME for example: we let it handle all key events.\n        return \"\";\n    }\n    const hotkey = [];\n\n    // ------- Modifiers -------\n    // Modifiers are pushed in ascending order to the hotkey.\n    if (isMacOS() ? ev.ctrlKey : ev.altKey) {\n        hotkey.push(\"alt\");\n    }\n    if (isMacOS() ? ev.metaKey : ev.ctrlKey) {\n        hotkey.push(\"control\");\n    }\n    if (ev.shiftKey) {\n        hotkey.push(\"shift\");\n    }\n\n    // ------- Key -------\n    let key = ev.key.toLowerCase();\n\n    // The browser space is natively \" \", we want \"space\" for esthetic reasons\n    if (key === \" \") {\n        key = \"space\";\n    }\n\n    // Identify if the user has tapped on the number keys above the text keys.\n    if (ev.code && ev.code.indexOf(\"Digit\") === 0) {\n        key = ev.code.slice(-1);\n    }\n    // Prefer physical keys for non-latin keyboard layout.\n    if (!AUTHORIZED_KEYS.includes(key) && ev.code && ev.code.indexOf(\"Key\") === 0) {\n        key = ev.code.slice(-1).toLowerCase();\n    }\n    // Make sure we do not duplicate a modifier key\n    if (!MODIFIERS.includes(key)) {\n        hotkey.push(key);\n    }\n\n    return hotkey.join(\"+\");\n}\n\nexport const hotkeyService = {\n    dependencies: [\"ui\"],\n    // Be aware that all odoo hotkeys are designed with this modifier in mind,\n    // so changing the overlay modifier may conflict with some shortcuts.\n    overlayModifier: \"alt\",\n    start(env, { ui }) {\n        /** @type {Map<number, HotkeyRegistration>} */\n        const registrations = new Map();\n        let nextToken = 0;\n        let overlaysVisible = false;\n\n        addListeners(browser);\n\n        function addListeners(target) {\n            target.addEventListener(\"keydown\", onKeydown);\n            target.addEventListener(\"keyup\", removeHotkeyOverlays);\n            target.addEventListener(\"blur\", removeHotkeyOverlays);\n            target.addEventListener(\"click\", removeHotkeyOverlays);\n        }\n\n        /**\n         * Handler for keydown events.\n         * Verifies if the keyboard event can be dispatched or not.\n         * Rules sequence to forbid dispatching :\n         * - UI is blocked\n         * - the pressed key is not whitelisted\n         *\n         * @param {KeyboardEvent} event\n         */\n        function onKeydown(event) {\n            if (event.code && event.code.indexOf(\"Numpad\") === 0 && /^\\d$/.test(event.key)) {\n                // Ignore all number keys from the Keypad because of a certain input method\n                // of (advance-)ASCII characters on Windows OS: ALT+[numerical code from keypad]\n                // See https://support.microsoft.com/en-us/office/insert-ascii-or-unicode-latin-based-symbols-and-characters-d13f58d3-7bcb-44a7-a4d5-972ee12e50e0#bm1\n                return;\n            }\n\n            const hotkey = getActiveHotkey(event);\n            if (!hotkey) {\n                return;\n            }\n            const { activeElement, isBlocked } = ui;\n\n            // Do not dispatch if UI is blocked\n            if (isBlocked) {\n                return;\n            }\n\n            // Replace all [accesskey] attrs by [data-hotkey] on all elements.\n            // This is needed to take over on the default accesskey behavior\n            // and also to avoid any conflict with it.\n            const elementsWithAccessKey = document.querySelectorAll(\"[accesskey]\");\n            for (const el of elementsWithAccessKey) {\n                if (el instanceof HTMLElement) {\n                    el.dataset.hotkey = el.accessKey;\n                    el.removeAttribute(\"accesskey\");\n                }\n            }\n\n            // Special case: open hotkey overlays\n            if (!overlaysVisible && hotkey === hotkeyService.overlayModifier) {\n                addHotkeyOverlays(activeElement);\n                event.preventDefault();\n                return;\n            }\n\n            // Is the pressed key NOT whitelisted ?\n            const singleKey = hotkey.split(\"+\").pop();\n            if (!AUTHORIZED_KEYS.includes(singleKey)) {\n                return;\n            }\n\n            // Protect any editable target that does not explicitly accept hotkeys\n            // NB: except for ESC, which is always allowed as hotkey in editables.\n            const targetIsEditable =\n                event.target instanceof HTMLElement &&\n                (/input|textarea/i.test(event.target.tagName) || event.target.isContentEditable) &&\n                !event.target.matches(\"input[type=checkbox], input[type=radio]\");\n            const shouldProtectEditable =\n                targetIsEditable && !event.target.dataset.allowHotkeys && singleKey !== \"escape\";\n\n            // Finally, prepare and dispatch.\n            const infos = {\n                activeElement,\n                hotkey,\n                isRepeated: event.repeat,\n                target: event.target,\n                shouldProtectEditable,\n            };\n            const dispatched = dispatch(infos);\n            if (dispatched) {\n                // Only if event has been handled.\n                // Purpose: prevent browser defaults\n                event.preventDefault();\n                // Purpose: stop other window keydown listeners (e.g. home menu)\n                event.stopImmediatePropagation();\n            }\n\n            // Finally, always remove overlays at that point\n            if (overlaysVisible) {\n                removeHotkeyOverlays();\n                event.preventDefault();\n            }\n        }\n\n        /**\n         * Dispatches an hotkey to first matching registration.\n         * Registrations are iterated in following order:\n         * - priority to all registrations done through the hotkeyService.add()\n         *   method (NB: in descending order of insertion = newer first)\n         * - then all registrations done through the DOM [data-hotkey] attribute\n         *\n         * @param {{\n         *  activeElement: HTMLElement,\n         *  hotkey: string,\n         *  isRepeated: boolean,\n         *  target: EventTarget,\n         *  shouldProtectEditable: boolean,\n         * }} infos\n         * @returns {boolean} true if has been dispatched\n         */\n        function dispatch(infos) {\n            const { activeElement, hotkey, isRepeated, target, shouldProtectEditable } = infos;\n\n            // Prepare registrations and the common filter\n            const reversedRegistrations = Array.from(registrations.values()).reverse();\n            const domRegistrations = getDomRegistrations(hotkey, activeElement);\n            const allRegistrations = reversedRegistrations.concat(domRegistrations);\n\n            // Find all candidates\n            const candidates = allRegistrations.filter(\n                (reg) =>\n                    reg.hotkey === hotkey &&\n                    (reg.allowRepeat || !isRepeated) &&\n                    (reg.bypassEditableProtection || !shouldProtectEditable) &&\n                    (reg.global || reg.activeElement === activeElement) &&\n                    (!reg.isAvailable || reg.isAvailable(target)) &&\n                    (!reg.area || (target && reg.area() && reg.area().contains(target)))\n            );\n\n            // First candidate\n            let winner = candidates.shift();\n            if (winner && winner.area) {\n                // If there is an area, find the closest one\n                for (const candidate of candidates.filter((c) => Boolean(c.area))) {\n                    if (candidate.area() && winner.area().contains(candidate.area())) {\n                        winner = candidate;\n                    }\n                }\n            }\n\n            // Dispatch actual hotkey to the matching registration\n            if (winner) {\n                winner.callback({\n                    area: winner.area && winner.area(),\n                    target,\n                });\n                return true;\n            }\n            return false;\n        }\n\n        /**\n         * Get a list of registrations from the [data-hotkey] defined in the DOM\n         *\n         * @param {string} hotkey\n         * @param {HTMLElement} activeElement\n         * @returns {HotkeyRegistration[]}\n         */\n        function getDomRegistrations(hotkey, activeElement) {\n            const overlayModParts = hotkeyService.overlayModifier.split(\"+\");\n            if (!overlayModParts.every((el) => hotkey.includes(el))) {\n                return [];\n            }\n\n            // Get all elements having a data-hotkey attribute  and matching\n            // the actual hotkey without the overlayModifier.\n            const cleanHotkey = hotkey\n                .split(\"+\")\n                .filter((key) => !overlayModParts.includes(key))\n                .join(\"+\");\n            const elems = getVisibleElements(activeElement, `[data-hotkey='${cleanHotkey}' i]`);\n            return elems.map((el) => ({\n                hotkey,\n                activeElement,\n                bypassEditableProtection: true,\n                callback: () => {\n                    if (document.activeElement) {\n                        document.activeElement.blur();\n                    }\n                    el.focus();\n                    setTimeout(() => el.click());\n                },\n            }));\n        }\n\n        /**\n         * Add the hotkey overlays respecting the ui active element.\n         * @param {HTMLElement} activeElement\n         */\n        function addHotkeyOverlays(activeElement) {\n            // Gather the hotkeys to overlay registered through the useHotkey hook.\n            const hotkeysFromHookToHighlight = [];\n            for (const [, registration] of registrations) {\n                const overlayElement = registration.withOverlay?.();\n                if (overlayElement) {\n                    hotkeysFromHookToHighlight.push({\n                        hotkey: registration.hotkey.replace(\n                            `${hotkeyService.overlayModifier}+`,\n                            \"\"\n                        ),\n                        el: overlayElement,\n                    });\n                }\n            }\n\n            // Gather the hotkeys to overlay registered through the DOM datasets.\n            const hotkeysFromDomToHighlight = getVisibleElements(\n                activeElement,\n                \"[data-hotkey]:not(:disabled)\"\n            ).map((el) => ({ hotkey: el.dataset.hotkey, el }));\n\n            const items = [...hotkeysFromDomToHighlight, ...hotkeysFromHookToHighlight];\n            for (const item of items) {\n                const hotkey = item.hotkey;\n                const overlay = document.createElement(\"div\");\n                overlay.classList.add(\n                    \"o_web_hotkey_overlay\",\n                    \"position-absolute\",\n                    \"top-0\",\n                    \"bottom-0\",\n                    \"start-0\",\n                    \"end-0\",\n                    \"d-flex\",\n                    \"justify-content-center\",\n                    \"align-items-center\",\n                    \"m-0\",\n                    \"bg-black-50\",\n                    \"h6\"\n                );\n                overlay.style.zIndex = 1;\n                const overlayKbd = document.createElement(\"kbd\");\n                overlayKbd.className = \"small\";\n                overlayKbd.appendChild(document.createTextNode(hotkey.toUpperCase()));\n                overlay.appendChild(overlayKbd);\n\n                let overlayParent;\n                if (item.el.tagName.toUpperCase() === \"INPUT\") {\n                    // special case for the search input that has an access key\n                    // defined. We cannot set the overlay on the input itself,\n                    // only on its parent.\n                    overlayParent = item.el.parentElement;\n                } else {\n                    overlayParent = item.el;\n                }\n\n                if (overlayParent.style.position !== \"absolute\") {\n                    overlayParent.style.position = \"relative\";\n                }\n                overlayParent.appendChild(overlay);\n            }\n            overlaysVisible = true;\n        }\n\n        /**\n         * Remove all the hotkey overlays.\n         */\n        function removeHotkeyOverlays() {\n            for (const overlay of document.querySelectorAll(\".o_web_hotkey_overlay\")) {\n                overlay.remove();\n            }\n            overlaysVisible = false;\n        }\n\n        /**\n         * Registers a new hotkey.\n         *\n         * @param {string} hotkey\n         * @param {HotkeyCallback} callback\n         * @param {HotkeyOptions} [options]\n         * @returns {number} registration token\n         */\n        function registerHotkey(hotkey, callback, options = {}) {\n            // Validate some informations\n            if (!hotkey || hotkey.length === 0) {\n                throw new Error(\"You must specify an hotkey when registering a registration.\");\n            }\n\n            if (!callback || typeof callback !== \"function\") {\n                throw new Error(\n                    \"You must specify a callback function when registering a registration.\"\n                );\n            }\n\n            /**\n             * An hotkey must comply to these rules:\n             *  - all parts are whitelisted\n             *  - single key part comes last\n             *  - each part is separated by the dash character: \"+\"\n             */\n            const keys = hotkey\n                .toLowerCase()\n                .split(\"+\")\n                .filter((k) => !MODIFIERS.includes(k));\n            if (keys.some((k) => !AUTHORIZED_KEYS.includes(k))) {\n                throw new Error(\n                    `You are trying to subscribe for an hotkey ('${hotkey}')\n            that contains parts not whitelisted: ${keys.join(\", \")}`\n                );\n            } else if (keys.length > 1) {\n                throw new Error(\n                    `You are trying to subscribe for an hotkey ('${hotkey}')\n            that contains more than one single key part: ${keys.join(\"+\")}`\n                );\n            }\n\n            // Add registration\n            const token = nextToken++;\n            /** @type {HotkeyRegistration} */\n            const registration = {\n                hotkey: hotkey.toLowerCase(),\n                callback,\n                activeElement: null,\n                allowRepeat: options && options.allowRepeat,\n                bypassEditableProtection: options && options.bypassEditableProtection,\n                global: options && options.global,\n                area: options && options.area,\n                isAvailable: options && options.isAvailable,\n                withOverlay: options && options.withOverlay,\n            };\n\n            // Due to the way elements are mounted in the DOM by Owl (bottom-to-top),\n            // we need to wait the next micro task tick to set the context owner of the registration.\n            Promise.resolve().then(() => {\n                registration.activeElement = ui.activeElement;\n            });\n\n            registrations.set(token, registration);\n            return token;\n        }\n\n        /**\n         * Unsubscribes the token corresponding registration.\n         *\n         * @param {number} token\n         */\n        function unregisterHotkey(token) {\n            registrations.delete(token);\n        }\n\n        return {\n            /**\n             * @param {string} hotkey\n             * @param {HotkeyCallback} callback\n             * @param {HotkeyOptions} [options]\n             * @returns {() => void}\n             */\n            add(hotkey, callback, options = {}) {\n                const token = registerHotkey(hotkey, callback, options);\n                return () => {\n                    unregisterHotkey(token);\n                };\n            },\n            /**\n             * @param {HTMLIFrameElement} iframe\n             */\n            registerIframe(iframe) {\n                addListeners(iframe.contentWindow);\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"hotkey\", hotkeyService);\n/** @typedef {ReturnType<hotkeyService[\"start\"]>} HotkeyService */\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { Component, onMounted, useState } from \"@odoo/owl\";\nimport { isDisplayStandalone } from \"@web/core/browser/feature_detection\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\n\nexport class InstallScopedApp extends Component {\n    static props = {};\n    static template = \"web.InstallScopedApp\";\n    static components = { Dropdown };\n    setup() {\n        this.pwa = useService(\"pwa\");\n        this.state = useState({ manifest: {}, showInstallUI: false });\n        this.isDisplayStandalone = isDisplayStandalone();\n        // BeforeInstallPrompt event can take while before the browser triggers it. Some will display\n        // immediately, others will wait that the user has interacted for some time with the website.\n        this.isInstallationPossible = browser.BeforeInstallPromptEvent !== undefined;\n        onMounted(async () => {\n            this.state.manifest = await this.pwa.getManifest();\n            this.state.showInstallUI = true;\n        });\n    }\n    onChangeName(ev) {\n        const value = ev.target.value;\n        if (value !== this.state.manifest.name) {\n            const url = new URL(document.location.href);\n            url.searchParams.set(\"app_name\", encodeURIComponent(value));\n            browser.location.replace(url);\n        }\n    }\n    onInstall() {\n        this.state.showInstallUI = false;\n        this.pwa.show({\n            onDone: (res) => {\n                if (res.outcome === \"accepted\") {\n                    browser.location.replace(this.state.manifest.start_url);\n                } else {\n                    this.state.showInstallUI = true;\n                }\n            },\n        });\n    }\n}\n\nregistry.category(\"public_components\").add(\"web.install_scoped_app\", InstallScopedApp);\n", "/** @odoo-module **/\nimport { useEffect, onMounted } from \"@odoo/owl\";\nimport { CodeEditor } from \"@web/core/code_editor/code_editor\";\nimport { escapeRegExp } from \"@web/core/utils/strings\";\n\nexport class IrUiViewCodeEditor extends CodeEditor {\n    static props = {\n        ...this.props,\n        record: { type: Object },\n    };\n\n    setup() {\n        super.setup(...arguments);\n        this.markers = [];\n\n        onMounted(() => {\n            this.aceEditor.getSession().on(\"change\", () => {\n                // Markers have fixed pixel positions, so they get wonky on change.\n                this.clearMarkers();\n            });\n        });\n\n        useEffect(\n            (arch, invalid_locators) => {\n                if (arch && invalid_locators) {\n                    this.highlightInvalidLocators(arch, invalid_locators);\n                    return () => this.clearMarkers();\n                }\n            },\n            () => [this.props.value, this.props.record?.data.invalid_locators]\n        );\n    }\n\n    async highlightInvalidLocators(arch, invalid_locators) {\n        const resModel = this.env.model?.config.resModel;\n        const resId = this.env.model?.config.resId;\n        if (resModel === \"ir.ui.view\" && resId) {\n            const { doc } = this.aceEditor.session;\n            for (const spec of invalid_locators) {\n                if (spec.broken_hierarchy) {\n                    continue\n                }\n                const { tag, attrib, sourceline } = spec;\n                const attribRegex = Object.entries(attrib)\n                    .map(([key, value]) => {\n                        const escapedValue = escapeRegExp(value).replace(/\"/g, '(\"|&quot;)');\n                        return (\n                            `(?=[^>]*?\\\\b${escapeRegExp(key)}\\\\s*=\\\\s*` +\n                            `(?:\"[^\"]*${escapedValue}[^\"]*\"|'[^']*${escapedValue}[^']*'))`\n                        );\n                    })\n                    .join(\"\");\n                const nodeRegex = new RegExp(`<${escapeRegExp(tag)}\\\\s+${attribRegex}[^>]*>`, \"g\");\n                for (const match of arch.matchAll(nodeRegex)) {\n                    const startIndex = match.index;\n                    const endIndex = startIndex + match[0].length;\n                    const startPos = doc.indexToPosition(startIndex);\n                    const endPos = doc.indexToPosition(endIndex);\n                    if (startPos.row + 1 === sourceline) {\n                        const range = new window.ace.Range(\n                            startPos.row,\n                            startPos.column,\n                            endPos.row,\n                            endPos.column\n                        );\n                        this.markers.push(\n                            this.aceEditor.session.addMarker(range, \"invalid_locator\", \"text\")\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    clearMarkers() {\n        this.markers.forEach((marker) => this.aceEditor.session.removeMarker(marker));\n        this.markers = [];\n    }\n}\n", "import { localization } from \"@web/core/l10n/localization\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { ensureArray } from \"../utils/arrays\";\n\nconst { DateTime, Settings } = luxon;\n\n/**\n * @typedef ConversionOptions\n *  This is a list of the available options to either:\n *  - convert a DateTime to a string (format)\n *  - convert a string to a DateTime (parse)\n *  All of these are optional and the default values are issued by the Localization service.\n *\n * @property {string} [format]\n *  Format used to format a DateTime or to parse a formatted string.\n *  > Default: the session localization format.\n *\n * @typedef {luxon.DateTime} DateTime\n *\n * @typedef {[NullableDateTime, NullableDateTime]} NullableDateRange\n *\n * @typedef {DateTime | false | null | undefined} NullableDateTime\n */\n\n/**\n * @typedef ConversionLocalOptions\n *\n * @property {boolean} [showSeconds]\n *  Show the seconds in the final result.\n *  > Default: false.\n * @property {boolean} [showTime]\n *  Show the time in the final result.\n *  > Default: true.\n * @property {boolean} [showDate]\n *  Show the date in the final result.\n *  > Default: true.\n */\n\n/**\n * Limits defining a valid date.\n * This is needed because the server only understands 4-digit years.\n * Note: both of these are in the local timezone\n */\nexport const MIN_VALID_DATE = DateTime.fromObject({ year: 1000 });\nexport const MAX_VALID_DATE = DateTime.fromObject({ year: 9999 }).endOf(\"year\");\n\nconst SERVER_DATE_FORMAT = \"yyyy-MM-dd\";\nconst SERVER_TIME_FORMAT = \"HH:mm:ss\";\nconst SERVER_DATETIME_FORMAT = `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`;\n\nconst nonAlphaRegex = /[^a-z]/gi;\nconst nonDigitRegex = /[^\\d]/g;\n\nconst normalizeFormatTable = {\n    // Python strftime to luxon.js conversion table\n    // See odoo/addons/base/views/res_lang_views.xml\n    // for details about supported directives\n    a: \"ccc\",\n    A: \"cccc\",\n    b: \"MMM\",\n    B: \"MMMM\",\n    d: \"dd\",\n    H: \"HH\",\n    I: \"hh\",\n    j: \"o\",\n    m: \"MM\",\n    M: \"mm\",\n    p: \"a\",\n    S: \"ss\",\n    W: \"WW\",\n    w: \"c\",\n    y: \"yy\",\n    Y: \"yyyy\",\n    c: \"ccc MMM d HH:mm:ss yyyy\",\n    x: \"MM/dd/yy\",\n    X: \"HH:mm:ss\",\n};\n\nconst smartDateUnits = {\n    d: \"days\",\n    m: \"months\",\n    w: \"weeks\",\n    y: \"years\",\n    H: \"hours\",\n    M: \"minutes\",\n    S: \"seconds\",\n};\nconst smartWeekdays = {\n    monday: 1,\n    tuesday: 2,\n    wednesday: 3,\n    thursday: 4,\n    friday: 5,\n    saturday: 6,\n    sunday: 7,\n};\n\n/** @type {WeakMap<DateTime, string>} */\nconst dateCache = new WeakMap();\n/** @type {WeakMap<DateTime, string>} */\nconst dateTimeCache = new WeakMap();\n\nexport class ConversionError extends Error {\n    name = \"ConversionError\";\n}\n\n//-----------------------------------------------------------------------------\n// Helpers\n//-----------------------------------------------------------------------------\n\n/**\n * Checks whether 2 given dates or date ranges are equal. Both values are allowed\n * to be falsy or to not be of the same type (which will return false).\n *\n * @param {NullableDateTime | NullableDateRange} d1\n * @param {NullableDateTime | NullableDateRange} d2\n * @returns {boolean}\n */\nexport function areDatesEqual(d1, d2) {\n    if (Array.isArray(d1) || Array.isArray(d2)) {\n        // One of the values is a date range -> checks deep equality between the ranges\n        d1 = ensureArray(d1);\n        d2 = ensureArray(d2);\n        return d1.length === d2.length && d1.every((d1Val, i) => areDatesEqual(d1Val, d2[i]));\n    }\n    if (d1 instanceof DateTime && d2 instanceof DateTime && d1 !== d2) {\n        // Both values are DateTime objects -> use Luxon's comparison\n        return d1.equals(d2);\n    } else {\n        // One of the values is not a DateTime object -> fallback to strict equal\n        return d1 === d2;\n    }\n}\n\n/**\n * @param {DateTime} desired\n * @param {DateTime} minDate\n * @param {DateTime} maxDate\n */\nexport function clampDate(desired, minDate, maxDate) {\n    if (maxDate < desired) {\n        return maxDate;\n    }\n    if (minDate > desired) {\n        return minDate;\n    }\n    return desired;\n}\n\n/**\n * Get the week year and week number of a given date as well as the starting\n * date of the week, in the user's locale settings.\n *\n * @param {Date | luxon.DateTime} date\n * @returns {{ year: number, week: number, startDate: luxon.DateTime }}\n *  the year the week is part of, and\n *  the ISO week number (1-53) of the Monday nearest to the locale's first day of the week\n */\nexport function getLocalYearAndWeek(date) {\n    if (!date.isLuxonDateTime) {\n        date = DateTime.fromJSDate(date);\n    }\n    const { weekStart } = localization;\n    // go to start of week\n    const startDate = date.minus({ days: (date.weekday + 7 - weekStart) % 7 });\n    // go to nearest Monday, up to 3 days back- or forwards\n    date =\n        weekStart > 1 && weekStart < 5 // if firstDay after Mon & before Fri\n            ? startDate.minus({ days: (startDate.weekday + 6) % 7 }) // then go back 1-3 days\n            : startDate.plus({ days: (8 - startDate.weekday) % 7 }); // else go forwards 0-3 days\n    date = date.plus({ days: 6 }); // go to last weekday of ISO week\n    const jan4 = DateTime.local(date.year, 1, 4);\n    // count from previous year if week falls before Jan 4\n    const diffDays =\n        date < jan4 ? date.diff(jan4.minus({ years: 1 }), \"day\").days : date.diff(jan4, \"day\").days;\n    return {\n        year: date.year,\n        week: Math.trunc(diffDays / 7) + 1,\n        startDate,\n    };\n}\n\n/**\n * Get the start of the week for the given date, in the user's locale settings.\n * The start of the week is determined by the `weekStart` setting.\n *\n * Luxon's `.startOf(\"week\")` method uses the ISO week definition, which starts on Monday.\n * Luxon has a `.startOf(\"week\", { useLocaleWeeks: true })` method, but it relies on the\n * Intl API and the `getWeekInfo` method, which is not supported in all browsers.\n * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo#browser_compatibility\n *\n * @param {luxon.DateTime} date\n * @returns {luxon.DateTime}\n */\nexport function getStartOfLocalWeek(date) {\n    const { weekStart } = localization;\n    const weekday = date.weekday < weekStart ? weekStart - 7 : weekStart;\n    return date.set({ weekday }).startOf(\"day\");\n}\n\n/**\n * Get the end of the week for the given date, in the user's locale settings.\n * The end of the week is determined by the `weekStart` setting.\n *\n * Luxon's `.endOf(\"week\")` method uses the ISO week definition, which starts on Monday.\n * Luxon has a `.endOf(\"week\", { useLocaleWeeks: true })` method, but it relies on the\n * Intl API and the `getWeekInfo` method, which is not supported in all browsers.\n * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo#browser_compatibility\n *\n * @param {luxon.DateTime} date\n * @returns {luxon.DateTime}\n */\nexport function getEndOfLocalWeek(date) {\n    return getStartOfLocalWeek(date).plus({ days: 6 }).endOf(\"day\");\n}\n\n/**\n * @param {NullableDateTime | NullableDateRange} value\n * @param {NullableDateRange} range\n * @returns {boolean}\n */\nexport function isInRange(value, range) {\n    if (!value || !range) {\n        return false;\n    }\n    if (Array.isArray(value)) {\n        const actualValues = value.filter(Boolean).sort();\n        if (actualValues.length < 2) {\n            return isInRange(actualValues[0], range);\n        }\n        return (\n            (actualValues[0] <= range[0] && range[0] <= actualValues[1]) ||\n            (range[0] <= actualValues[0] && actualValues[0] <= range[1])\n        );\n    } else {\n        return range[0] <= value && value <= range[1];\n    }\n}\n\n/**\n * Returns whether the given DateTime is valid.\n * The date is considered valid if it:\n * - is a DateTime object\n * - has the \"isValid\" flag set to true\n * - is between 1000-01-01 and 9999-12-31 (both included)\n * @see MIN_VALID_DATE\n * @see MAX_VALID_DATE\n *\n * @param {NullableDateTime} date\n */\nfunction isValidDate(date) {\n    return date && date.isValid && isInRange(date, [MIN_VALID_DATE, MAX_VALID_DATE]);\n}\n\n/**\n * Smart date inputs are shortcuts to write dates quicker.\n * These shortcuts are based on python version: `odoo.tools.date_utils.parse_date`.\n * Starting from now (or \"today\"), add relative delta to the date.\n *\n * e.g.\n *   \"+1d\" or \"+1\" will return now + 1 day\n *   \"-2w\" will return now - 2 weeks\n *   \"+3m\" will return now + 3 months\n *   \"-4y\" will return now + 4 years\n *   \"=monday\" will return the previous Monday at midnight\n *   \"=1d\" will return the first day of month at midnight\n *   \"+3H\" will return now + 3 hours\n *   \"+3M\" will return now + 3 minutes\n *   \"+3S\" will return now + 3 seconds\n *   \"today -1d\" will return yesterday at midnight\n *   \"=week_start\" will return the first day of the current week at midnight, according to the locale\n *\n * Difference with python version: a simple \"+1\" means \"+1d\" for the first term,\n * the unit is optional and defaults to \"d\".\n *\n * @param {string} value\n * @returns {NullableDateTime} Luxon datetime object (in the user's local timezone)\n */\nfunction parseSmartDateInput(value) {\n    const terms = value.split(/\\s+/);\n    if (!terms.length) {\n        return false;\n    }\n    var now = DateTime.local().startOf(\"second\");\n    if (terms[0] == \"today\") {\n        terms.shift();\n        now = now.startOf(\"day\");\n    } else if (terms[0] == \"now\") {\n        terms.shift();\n    } else if (terms.length == 1 && /^[=+-]\\d+$/.test(terms[0])) {\n        // handle optional unit for simple input\n        terms[0] += \"d\";\n    }\n\n    for (let i = 0; i < terms.length; i++) {\n        const term = terms[i];\n        const operator = term[0];\n        if (term.length < 3 || ![\"+\", \"-\", \"=\"].includes(operator)) {\n            return false;\n        }\n\n        // Weekday\n        const dayname = term.slice(1);\n        if (Object.hasOwn(smartWeekdays, dayname) || dayname === \"week_start\") {\n            const { weekStart } = localization;\n            const weekdayNumber =\n                dayname === \"week_start\" ? weekStart : smartWeekdays[dayname];\n            let weekdayOffset =\n                ((weekdayNumber - weekStart + 7) % 7) - ((now.weekday - weekStart + 7) % 7);\n            if (operator == \"+\" || operator == \"-\") {\n                if (weekdayOffset > 0 && operator == \"-\") {\n                    weekdayOffset -= 7;\n                } else if (weekdayOffset < 0 && operator == \"+\") {\n                    weekdayOffset += 7;\n                }\n            } else {\n                now = now.startOf(\"day\");\n            }\n            now = now.plus({ days: weekdayOffset });\n            continue;\n        }\n\n        // Operations on dates\n        try {\n            const field_name = smartDateUnits[term[term.length - 1]];\n            const number = parseInt(term.slice(1, -1), 10);\n            if (!field_name || isNaN(number)) {\n                return false;\n            }\n            if (operator == \"+\") {\n                now = now.plus({ [field_name]: number });\n            } else if (operator == \"-\") {\n                now = now.minus({ [field_name]: number });\n            } else if (operator == \"=\") {\n                if (field_name == \"seconds\" || field_name == \"minutes\" || field_name == \"hours\") {\n                    now = now.startOf(field_name);\n                } else if (field_name == \"weeks\") {\n                    return false; // unsupported\n                } else {\n                    now = now.startOf(\"day\");\n                }\n                now = now.set({ [field_name]: number });\n            }\n        } catch {\n            return false;\n        }\n    }\n\n    return now;\n}\n\n/**\n * Removes any duplicate *subsequent* alphabetic characters in a given string.\n * Example: \"aa-bb-CCcc-ddD-c xxxx-Yy-ZZ\" -> \"a-b-Cc-dD-c x-Yy-Z\"\n *\n * @type {(str: string) => string}\n */\nconst stripAlphaDupes = memoize(function stripAlphaDupes(str) {\n    return str.replace(/[a-z]/gi, (letter, index, str) =>\n        letter === str[index - 1] ? \"\" : letter\n    );\n});\n\n/**\n * Convert Python strftime to escaped luxon.js format.\n *\n * @type {(format: string) => string}\n */\nexport const strftimeToLuxonFormat = memoize(function strftimeToLuxonFormat(format) {\n    const output = [];\n    let inToken = false;\n    for (let index = 0; index < format.length; ++index) {\n        let character = format[index];\n        if (character === \"%\" && !inToken) {\n            inToken = true;\n            continue;\n        }\n        if (/[a-z]/gi.test(character)) {\n            if (inToken && normalizeFormatTable[character] !== undefined) {\n                character = normalizeFormatTable[character];\n            } else {\n                character = `'${character}'`; // luxon escape\n            }\n        }\n        output.push(character);\n        inToken = false;\n    }\n    return output.join(\"\");\n});\n\n/**\n * Lazy getter returning the start of the current day.\n */\nexport function today() {\n    return DateTime.local().startOf(\"day\");\n}\n\n//-----------------------------------------------------------------------------\n// Formatting\n//-----------------------------------------------------------------------------\n\n/**\n * Formats a DateTime object to a date string\n *\n * @param {NullableDateTime} value\n * @param {ConversionOptions} [options={}]\n */\nexport function formatDate(value, options = {}) {\n    if (!value) {\n        return \"\";\n    }\n    const format = options.format || localization.dateFormat;\n    return value.toFormat(format);\n}\n\n/**\n * Formats a DateTime object to a datetime string\n *\n * @param {NullableDateTime} value\n * @param {ConversionOptions} [options={}]\n */\nexport function formatDateTime(value, options = {}) {\n    if (!value) {\n        return \"\";\n    }\n    const format = options.format || localization.dateTimeFormat;\n    return value.setZone(options.tz || \"default\").toFormat(format);\n}\n\n/**\n * Format a DateTime object to a locale date string.\n * e.g.: Jan 31, 2024\n * If the year is the current one, then it's omitted\n * from the result.\n *\n * @param {NullableDateTime} value\n */\nexport function toLocaleDateString(value) {\n    if (!value) {\n        return \"\";\n    }\n    const format = { ...DateTime.DATE_MED };\n    if (today().year === value.year) {\n        delete format.year;\n    }\n    return value.toLocaleString(format);\n}\n\n/**\n * Format a DateTime object to a locale datetime string\n * e.g.: Jan 31, 2024, 12:00 AM\n * If the year is the current one, then it's omitted\n * from the result.\n *\n * @param {NullableDateTime} value\n * @param {ConversionLocalOptions} [options={}]\n */\nexport function toLocaleDateTimeString(\n    value,\n    options = { showDate: true, showTime: true, showSeconds: false }\n) {\n    if (!value) {\n        return \"\";\n    }\n    const format = { ...DateTime.DATETIME_MED_WITH_SECONDS };\n    if (!options.showSeconds) {\n        delete format.second;\n    }\n    if (options.showDate === false) {\n        delete format.day;\n        delete format.month;\n        delete format.year;\n    }\n    if (options.showTime === false) {\n        delete format.hour;\n        delete format.minute;\n    }\n    if (today().year === value.year) {\n        delete format.year;\n    }\n    return value.setZone(options.tz || \"default\").toLocaleString(format);\n}\n\n/**\n * Converts a given duration in seconds into a human-readable format.\n *\n * The function takes a duration in seconds and converts it into a human-readable form,\n * such as \"1h\" or \"1 hour, 30 minutes\", depending on the value of the `showFullDuration` parameter.\n * If the `showFullDuration` is set to true, the function will display up to two non-zero duration\n * components in long form (e.g: hours, minutes).\n * Otherwise, it will show just the largest non-zero duration component in narrow form (e.g: y or h).\n * Luxon takes care of translations given the current locale.\n *\n * @param {number} seconds - The duration in seconds to be converted.\n * @param {boolean} showFullDuration - If true, the output will have two components in long form.\n * Otherwise, just one component will be displayed in narrow form.\n *\n * @returns {string} A human-readable string representation of the duration.\n *\n * @example\n * // Sample usage\n * const durationInSeconds = 7320; // 2 hours and 2 minutes (2 * 3600 + 2 * 60)\n * const fullDuration = humanizeDuration(durationInSeconds, true);\n * console.log(fullDuration); // Output: \"2 hours, 2 minutes\"\n *\n * const shortDuration = humanizeDuration(durationInSeconds, false);\n * console.log(shortDuration); // Output: \"2h\"\n */\nexport function formatDuration(seconds, showFullDuration) {\n    const displayStyle = showFullDuration ? \"long\" : \"narrow\";\n    const numberOfValuesToDisplay = showFullDuration ? 2 : 1;\n    const durationKeys = [\"years\", \"months\", \"days\", \"hours\", \"minutes\"];\n\n    if (seconds < 60) {\n        seconds = 60;\n    }\n    seconds -= seconds % 60;\n\n    let duration = luxon.Duration.fromObject({ seconds: seconds }).shiftTo(...durationKeys);\n    duration = duration.shiftTo(...durationKeys.filter((key) => duration.get(key)));\n    const durationSplit = duration.toHuman({ unitDisplay: displayStyle }).split(\",\");\n\n    if (!showFullDuration && duration.loc.locale.includes(\"en\") && duration.months > 0) {\n        durationSplit[0] = durationSplit[0].replace(\"m\", \"M\");\n    }\n    return durationSplit.slice(0, numberOfValuesToDisplay).join(\",\");\n}\n\n/**\n * Formats the given DateTime to the server date format.\n * @param {DateTime} value\n * @returns {string}\n */\nexport function serializeDate(value) {\n    if (!dateCache.has(value)) {\n        dateCache.set(value, value.toFormat(SERVER_DATE_FORMAT, { numberingSystem: \"latn\" }));\n    }\n    return dateCache.get(value);\n}\n\n/**\n * Formats the given DateTime to the server datetime format.\n * @param {DateTime} value\n * @returns {string}\n */\nexport function serializeDateTime(value) {\n    if (!dateTimeCache.has(value)) {\n        dateTimeCache.set(\n            value,\n            value.setZone(\"utc\").toFormat(SERVER_DATETIME_FORMAT, { numberingSystem: \"latn\" })\n        );\n    }\n    return dateTimeCache.get(value);\n}\n\n//-----------------------------------------------------------------------------\n// Parsing\n//-----------------------------------------------------------------------------\n\n/**\n * Parses a string value to a Luxon DateTime object.\n *\n * @param {string} value\n * @param {ConversionOptions} [options={}]\n *\n * @see parseDateTime (Note: since we're only interested by the date itself, the\n *  returned value will always be set at the start of the day)\n */\nexport function parseDate(value, options = {}) {\n    const parsed = parseDateTime(value, {\n        ...options,\n        format: options.format || localization.dateFormat,\n    });\n    return parsed && parsed.startOf(\"day\");\n}\n\n/**\n * Parses a string value to a Luxon DateTime object.\n *\n * @param {string} value value to parse.\n *  - Value can take the form of a smart date:\n *    e.g. \"+3w\" for three weeks from now.\n *    (`options.format` is ignored in this case)\n *\n *  - If value cannot be parsed within the provided format,\n *    ISO8601 and SQL formats are then tried. If these formats\n *    include a timezone information, the returned value will\n *    still be set to the user's timezone.\n *    e.g. \"2020-01-01T12:00:00+06:00\" with the user's timezone being UTC+1,\n *         the returned value will express the same timestamp but in UTC+1 (here time will be 7:00).\n *\n * @param {ConversionOptions} options\n *\n * @returns {NullableDateTime} Luxon DateTime object in user's timezone\n */\nexport function parseDateTime(value, options = {}) {\n    if (!value) {\n        return false;\n    }\n\n    const fmt = options.format || localization.dateTimeFormat;\n    const parseOpts = {\n        setZone: true,\n        zone: options.tz || \"default\",\n    };\n    const switchToLatin = Settings.defaultNumberingSystem !== \"latn\" && /[0-9]/.test(value);\n\n    // Force numbering system to latin if actual numbers are found in the value\n    if (switchToLatin) {\n        parseOpts.numberingSystem = \"latn\";\n    }\n\n    // Base case: try parsing with the given format and options\n    let result = DateTime.fromFormat(value, fmt, parseOpts);\n\n    // Try parsing as a smart date\n    if (!isValidDate(result)) {\n        result = parseSmartDateInput(value);\n    }\n\n    // Try parsing with partial date parts\n    if (!isValidDate(result)) {\n        const fmtWoZero = stripAlphaDupes(fmt);\n        result = DateTime.fromFormat(value, fmtWoZero, parseOpts);\n    }\n\n    // Try parsing with custom shorthand date parts\n    if (!isValidDate(result)) {\n        // Luxon is not permissive regarding delimiting characters in the format.\n        // So if the value to parse has less characters than the format, we would\n        // try to parse without the delimiting characters.\n        const digitList = value.split(nonDigitRegex).filter(Boolean);\n        const fmtList = fmt.split(nonAlphaRegex).filter(Boolean);\n        const valWoSeps = digitList.join(\"\");\n\n        // This is the weird part: we try to adapt the given format to comply with\n        // the amount of digits in the given value. To do this we split the format\n        // and the value on non-letter and non-digit characters respectively. This\n        // should create the same amount of grouping parameters, and the format\n        // groups are trimmed according to the length of their corresponding\n        // digit group. The 'carry' variable allows for the length of a digit\n        // group to overflow to the next format group. This is typically the case\n        // when the given value doesn't have non-digit separators and generates\n        // one big digit group instead.\n        let carry = 0;\n        const fmtWoSeps = fmtList\n            .map((part, i) => {\n                const digitLength = (digitList[i] || \"\").length;\n                const actualPart = part.slice(0, digitLength + carry);\n                carry += digitLength - actualPart.length;\n                return actualPart;\n            })\n            .join(\"\");\n\n        result = DateTime.fromFormat(valWoSeps, fmtWoSeps, parseOpts);\n    }\n\n    // Try with defaul ISO or SQL formats\n    if (!isValidDate(result)) {\n        // Also try some fallback formats, but only if value counts more than\n        // four digit characters as this could get misinterpreted as the time of\n        // the actual date.\n        const valueDigits = value.replace(nonDigitRegex, \"\");\n        if (valueDigits.length > 4) {\n            result = DateTime.fromISO(value, parseOpts); // ISO8601\n            if (!isValidDate(result)) {\n                result = DateTime.fromSQL(value, parseOpts); // last try: SQL\n            }\n        }\n    }\n\n    // No working parsing methods: throw an error\n    if (!isValidDate(result)) {\n        throw new ConversionError(_t(\"'%s' is not a correct date or datetime\", value));\n    }\n\n    // Revert to original numbering system\n    if (switchToLatin) {\n        result = result.reconfigure({\n            numberingSystem: Settings.defaultNumberingSystem,\n        });\n    }\n\n    return result.setZone(options.tz || \"default\");\n}\n\n/**\n * Returns a date object parsed from the given serialized string.\n * @param {string} value serialized date string, e.g. \"2018-01-01\"\n */\nexport function deserializeDate(value, options = {}) {\n    options = { numberingSystem: \"latn\", zone: \"default\", ...options };\n    return DateTime.fromSQL(value, options).reconfigure({\n        numberingSystem: Settings.defaultNumberingSystem,\n    });\n}\n\n/**\n * Returns a datetime object parsed from the given serialized string.\n * @param {string} value serialized datetime string, e.g. \"2018-01-01 00:00:00\", expressed in UTC\n */\nexport function deserializeDateTime(value, options = {}) {\n    return DateTime.fromSQL(value, { numberingSystem: \"latn\", zone: \"utc\" })\n        .setZone(options?.tz || \"default\")\n        .reconfigure({\n            numberingSystem: Settings.defaultNumberingSystem,\n        });\n}\n", "/**\n * @typedef Localization\n * @property {string} dateFormat\n * @property {string} dateTimeFormat\n * @property {string} timeFormat\n * @property {string} decimalPoint\n * @property {\"ltr\" | \"rtl\"} direction\n * @property {[number, number]} grouping\n * @property {boolean} multiLang\n * @property {string} thousandsSep\n * @property {number} weekStart\n * @property {string} code\n */\n\n/**\n * This is the main object holding user specific data about the localization. Its basically\n * the JS counterpart of the \"res.lang\" model.\n * It is useful to directly access those data anywhere, even outside Components.\n *\n * Important Note: its data are actually loaded by the localization_service,\n * so a code like the following would not work:\n *   import { localization } from \"@web/core/l10n/localization\";\n *   const dateFormat = localization.dateFormat; // dateFormat isn't set yet\n * @type {Localization}\n */\nexport const localization = new Proxy(\n    {},\n    {\n        get: (target, p) => {\n            // \"then\" can be called implicitly if the object is returned in an\n            // `async` function, so we need to allow it.\n            if (p in target || p === \"then\") {\n                return Reflect.get(target, p);\n            }\n            throw new Error(\n                `could not access localization parameter \"${p}\": parameters are not ready yet. Maybe add 'localization' to your dependencies?`\n            );\n        },\n    }\n);\n", "import { session } from \"@web/session\";\nimport { jsToPyLocale } from \"@web/core/l10n/utils\";\nimport { user } from \"@web/core/user\";\nimport { browser } from \"../browser/browser\";\nimport { registry } from \"../registry\";\nimport { strftimeToLuxonFormat } from \"./dates\";\nimport { localization } from \"./localization\";\nimport {\n    translatedTerms,\n    translatedTermsGlobal,\n    translationLoaded,\n    translationIsReady,\n} from \"./translation\";\nimport { objectToUrlEncodedString } from \"../utils/urls\";\nimport { IndexedDB } from \"../utils/indexed_db\";\n\nconst { Settings } = luxon;\n\n/** @type {[RegExp, string][]} */\nconst NUMBERING_SYSTEMS = [\n    [/^ar-(sa|sy|001)$/i, \"arab\"],\n    [/^bn/i, \"beng\"],\n    [/^bo/i, \"tibt\"],\n    // [/^fa/i, \"Farsi (Persian)\"], // No numberingSystem found in Intl\n    // [/^(hi|mr|ne)/i, \"Hindi\"], // No numberingSystem found in Intl\n    // [/^my/i, \"Burmese\"], // No numberingSystem found in Intl\n    [/^pa-in/i, \"guru\"],\n    [/^ta/i, \"tamldec\"],\n    [/.*/i, \"latn\"],\n];\n\nexport const localizationService = {\n    start: async () => {\n        const localizationDB = new IndexedDB(\"localization\", session.registry_hash);\n        const translationURL = session.translationURL || \"/web/webclient/translations\";\n        const lang = jsToPyLocale(user.lang || document.documentElement.getAttribute(\"lang\"));\n\n        const fetchTranslations = async (hash) => {\n            let queryString = objectToUrlEncodedString({ hash, lang });\n            queryString = queryString.length > 0 ? `?${queryString}` : queryString;\n            const response = await browser.fetch(`${translationURL}${queryString}`, {\n                cache: \"no-store\",\n            });\n            if (!response.ok) {\n                throw new Error(\"Error while fetching translations\");\n            }\n            const result = await response.json();\n            if (result.hash !== hash) {\n                localizationDB.write(translationURL, JSON.stringify({ lang }), result);\n                updateTranslations(result);\n            }\n        };\n\n        const updateTranslations = (result) => {\n            // Eventually, we want a new python route to return directly the good result.\n            const terms = {};\n            for (const addon of Object.keys(result.modules)) {\n                terms[addon] = {};\n                for (const message of result.modules[addon].messages) {\n                    terms[addon][message.id] = message.string;\n                    translatedTermsGlobal[message.id] = message.string;\n                }\n            }\n            Object.assign(translatedTerms, terms);\n\n            const userLocalization = result.lang_parameters;\n            const dateFormat = strftimeToLuxonFormat(userLocalization.date_format);\n            const timeFormat = strftimeToLuxonFormat(userLocalization.time_format);\n\n            Object.assign(localization, {\n                dateFormat,\n                timeFormat,\n                dateTimeFormat: `${dateFormat} ${timeFormat}`,\n                decimalPoint: userLocalization.decimal_point,\n                direction: userLocalization.direction,\n                grouping: JSON.parse(userLocalization.grouping),\n                multiLang: result.multi_lang,\n                thousandsSep: userLocalization.thousands_sep,\n                weekStart: userLocalization.week_start,\n            });\n        };\n\n        const storedTranslations = await localizationDB.read(\n            translationURL,\n            JSON.stringify({ lang })\n        );\n\n        const translationProm = fetchTranslations(storedTranslations?.hash);\n        if (storedTranslations) {\n            updateTranslations(storedTranslations);\n        } else {\n            await translationProm;\n        }\n\n        translatedTerms[translationLoaded] = true;\n        translationIsReady.resolve(true);\n\n        const locale = user.lang || browser.navigator.language;\n        Settings.defaultLocale = locale;\n        for (const [re, numberingSystem] of NUMBERING_SYSTEMS) {\n            if (re.test(locale)) {\n                Settings.defaultNumberingSystem = numberingSystem;\n                break;\n            }\n        }\n        localization.code = jsToPyLocale(locale);\n        return localization;\n    },\n};\n\nregistry.category(\"services\").add(\"localization\", localizationService);\n", "import { localization } from \"@web/core/l10n/localization\";\n\nconst { DateTime } = luxon;\n\nconst NUMERAL_MAPS = [\n    \"\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\", // Arabic\n    \"\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\",\n    \"\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\", // Devanagari (Hindi)\n    \"\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0e50\", // Thai\n    \"\u96f6\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\", // Chinese/Japanese/Korean\n];\n\n/**\n * A representation of a specific time in a 24 hour format\n */\nexport class Time {\n    /**\n     * This method will return a Time object contructed\n     * differently depending on the type of {value}\n     *\n     * - If value is already a Time object, it returns it.\n     * - If value is null, undefined or false, it returns null.\n     * - If value is a string, it will try to parse it, @see {parseTime}\n     * - If value is an object, it will use its [hour], [minute] and [second] properties\n     * - Otherwise, return a new Time with default values\n     *\n     * @param {any} value\n     * @returns {Time|null}\n     */\n    static from(value) {\n        if (value === null || value === undefined || value === false) {\n            return null;\n        } else if (value instanceof Time) {\n            return value;\n        } else if (typeof value === \"string\") {\n            return parseTime(value, true);\n        } else if (typeof value === \"object\") {\n            return new Time(value);\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * @param {{\n     *  hour: 0,\n     *  minute: 0,\n     *  second: 0,\n     * }?} params\n     */\n    constructor({ hour = 0, minute = 0, second = 0 } = {}) {\n        /**@type {number} */\n        this.hour = hour;\n        /**@type {number} */\n        this.minute = minute;\n        /**@type {number} */\n        this.second = second;\n\n        /**\n         * @private\n         * @type {boolean}\n         */\n        this._is24HourFormat = is24HourFormat();\n\n        /**\n         * @private\n         * @type {boolean}\n         */\n        this._isMeridiemFormat = isMeridiemFormat();\n    }\n\n    /**\n     * @param {number} rounding\n     */\n    roundMinutes(rounding) {\n        this.minute = Math.round(this.minute / rounding) * rounding;\n    }\n\n    /**\n     * @returns {Time}\n     */\n    copy() {\n        return new Time(this);\n    }\n\n    /**\n     * @param {Time} other\n     * @param {boolean} [checkSeconds=false]\n     * @returns {boolean}\n     */\n    equals(other, checkSeconds = false) {\n        return (\n            other &&\n            this.hour === other.hour &&\n            this.minute === other.minute &&\n            (!checkSeconds || this.second === other.second)\n        );\n    }\n\n    /**\n     * Returns the formatted value of the time, with 24 of 12 hours\n     * format and with or without meridiems depending on the current\n     * localization time format.\n     *\n     * @param {boolean} [showSeconds=false]\n     * @returns {string}\n     */\n    toString(showSeconds = false) {\n        const hourFormat = this._is24HourFormat ? \"H\" : \"h\";\n        const secondFormat = showSeconds ? \":ss\" : \"\";\n        const meridiemFormat = this._isMeridiemFormat ? \"a\" : \"\";\n        return this.toDateTime()\n            .toFormat(`${hourFormat}:mm${secondFormat}${meridiemFormat}`)\n            .toLowerCase();\n    }\n\n    /**\n     * @returns {DateTime}\n     */\n    toDateTime() {\n        return DateTime.fromObject(this.toObject());\n    }\n\n    /**\n     * Returns the time as an Object\n     * @returns {{hour: number, minute: number, second: number}}\n     */\n    toObject() {\n        return {\n            hour: this.hour,\n            minute: this.minute,\n            second: this.second,\n        };\n    }\n}\n\n/**\n * Returns whether the given format is a 24-hour format.\n * Falls back to localization time format if none is given.\n *\n * @param {string} format\n */\nexport function is24HourFormat(format) {\n    return /H/.test(format || localization.timeFormat);\n}\n\n/**\n * Returns whether the given format uses a meridiem suffix (AM/PM).\n * Falls back to localization time format if none is given.\n *\n * @param {string} format\n */\nexport function isMeridiemFormat(format) {\n    return /a/.test(format || localization.timeFormat);\n}\n\n/**\n * Tries to parse a Time object from a time string\n * representation such as:\n * \"10:15\"  -> 10:15:00\n * \"2h5\"    -> 02:50:00\n * \"1015\"   -> 10:15:00\n * \"125\"    -> 12:50:00\n * \"315\"    -> 03:15:00\n * \"5:15pm\" -> 17:15:00\n *\n * Returns null if the value could not be parsed.\n *\n * @param {string} value\n * @param {boolean} [parseSeconds]\n * @returns {Time | null}\n */\nexport function parseTime(value, parseSeconds) {\n    const { isPm, isAm } = meridiemCheck(value);\n    value = normalizeTimeStr(value);\n\n    if (!value) {\n        return null;\n    }\n\n    let hour = 0;\n    let minute = 0;\n    let second = 0;\n\n    const parse = (str) => {\n        if (str.length === 0) {\n            return 0;\n        } else if (/^[\\d]+$/.test(str)) {\n            return parseInt(str, 10);\n        } else {\n            return NaN;\n        }\n    };\n\n    const parts = value.split(/[\\s:]/g);\n    if (parts.length > 3) {\n        return null;\n    } else if (parts.length === 3) {\n        if (!parseSeconds) {\n            return null;\n        }\n        hour = parse(parts[0]);\n        minute = parse(parts[1].padEnd(2, \"0\"));\n        second = parse(parts[2].padEnd(2, \"0\"));\n    } else if (parts.length === 2) {\n        hour = parse(parts[0]);\n        minute = parse(parts[1].padEnd(2, \"0\"));\n    } else if (parts.length === 1) {\n        const raw = parts[0];\n\n        const pickSolution = (...solutions) => {\n            for (const solution of solutions) {\n                const h = parse(solution[0]);\n                if (h <= 24) {\n                    hour = h;\n                    if (solution[1]) {\n                        minute = parse(solution[1].padEnd(2, \"0\"));\n                    }\n                    break;\n                }\n            }\n        };\n\n        if (raw.length == 1) {\n            hour = parse(raw);\n        } else if (raw.length == 2) {\n            pickSolution([raw], [raw[0], raw[1]]);\n        } else if (raw.length === 3) {\n            pickSolution([raw.slice(0, 2), raw[2]], [raw[0], raw.slice(1)]);\n        } else if (raw.length === 4) {\n            hour = parse(raw.slice(0, 2));\n            minute = parse(raw.slice(2));\n        } else if (raw.length > 4 && raw.length <= 6) {\n            if (!parseSeconds) {\n                return null;\n            }\n            hour = parse(raw.slice(0, 2));\n            minute = parse(raw.slice(2, 4));\n            second = parse(raw.slice(4).padEnd(2, \"0\"));\n        } else {\n            return null;\n        }\n    }\n\n    if (isPm && hour < 12) {\n        hour += 12;\n    } else if (isAm && hour === 12) {\n        hour = 0;\n    }\n\n    if (hour >= 0 && hour <= 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60) {\n        if (hour === 24) {\n            hour = 0;\n        }\n        return new Time({ hour, minute, second });\n    } else {\n        return null;\n    }\n}\n\n/**\n * - Converts other languages numeral systems to western arabic numbers\n * - Replaces with \":\" all chains of non-numeric characters between numbers\n * - Removes all trailing non-numeric characters\n *\n * @param {string} timeStr\n * @returns {string|false}\n */\nfunction normalizeTimeStr(timeStr) {\n    if (typeof timeStr !== \"string\") {\n        return false;\n    }\n\n    timeStr = timeStr.trim().toLowerCase();\n\n    for (const map of NUMERAL_MAPS) {\n        for (let i = 0; i < map.length; i++) {\n            timeStr = timeStr.replaceAll(map[i], i);\n        }\n    }\n\n    return timeStr.replace(/^\\D+|\\D+$/g, \"\").replace(/\\D+/g, \":\");\n}\n\n/**\n * @param {string} timeStr\n * @returns {{ isPm: boolean, isAm: boolean }}\n */\nfunction meridiemCheck(timeStr) {\n    const amPmMatch = typeof timeStr === \"string\" ? timeStr.toLowerCase().match(/(am|pm)/g) : false;\n    return {\n        isPm: amPmMatch && amPmMatch[0] === \"pm\",\n        isAm: amPmMatch && amPmMatch[0] === \"am\",\n    };\n}\n", "import { markup } from \"@odoo/owl\";\n\nimport { formatList } from \"@web/core/l10n/utils\";\nimport { isIterable } from \"@web/core/utils/arrays\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { htmlSprintf } from \"@web/core/utils/html\";\nimport { isObject } from \"@web/core/utils/objects\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nexport const translationLoaded = Symbol(\"translationLoaded\");\nexport const translatedTerms = {\n    [translationLoaded]: false,\n};\n/**\n * Contains all the translated terms. Unlike \"translatedTerms\", there is no\n * \"namespacing\" by module. It is used as a fallback when no translation is\n * found within the module's context, or when the context is not known.\n */\nexport const translatedTermsGlobal = {};\nexport const translationIsReady = new Deferred();\n\nconst Markup = markup().constructor;\n\n/**\n * Translates a term, or returns the term as it is if no translation can be\n * found.\n *\n * Extra positional arguments are inserted in place of %s placeholders.\n *\n * If the first extra argument is an object, the keys of that object are used to\n * map its entries to keyworded placeholders (%(kw_placeholder)s) for\n * replacement.\n *\n * If one or more of the extra arguments are iterables, they will be turned\n * into language-specific formatted strings representing the elements of the\n * list.\n *\n * If at least one of the extra arguments is a markup, the translation and\n * non-markup content are escaped, and the result is wrapped in a markup.\n *\n * @example\n * _t(\"Good morning\"); // \"Bonjour\"\n * _t(\"Good morning %s\", user.name); // \"Bonjour Marc\"\n * _t(\"Good morning %(newcomer)s, goodbye %(departer)s\", { newcomer: Marc, departer: Mitchel }); // Bonjour Marc, au revoir Mitchel\n * _t(\"I love %s\", markup`<blink>Minecraft</blink>`); // Markup {\"J'adore <blink>Minecraft</blink>\"}\n * _t(\"Good morning %s!\", [\"Mitchell\", \"Marc\", \"Louis\"]); // Bonjour Mitchell, Marc et Louis !\n *\n * @param {string} term\n * @returns {string|Markup|LazyTranslatedString}\n */\nexport function _t(term, ...values) {\n    if (translatedTerms[translationLoaded]) {\n        const translation = _getTranslation(term, odoo.translationContext);\n        if (values.length === 0) {\n            return translation;\n        }\n        return _safeFormatAndSprintf(translation, ...values);\n    } else {\n        return new LazyTranslatedString(term, values);\n    }\n}\n\nclass LazyTranslatedString extends String {\n    constructor(term, values) {\n        super(term);\n        this.translationContext = odoo.translationContext;\n        this.values = values;\n    }\n    valueOf() {\n        const term = super.valueOf();\n        if (translatedTerms[translationLoaded]) {\n            const translation = _getTranslation(term, this.translationContext);\n            if (this.values.length === 0) {\n                return translation;\n            }\n            return _safeFormatAndSprintf(translation, ...this.values);\n        } else {\n            throw new Error(`translation error`);\n        }\n    }\n    toString() {\n        return this.valueOf();\n    }\n}\n\n/**\n * Load the installed languages long names and code\n *\n * The result of the call is put in cache.\n * If any new language is installed, a full page refresh will happen,\n * so there is no need invalidate it.\n */\nexport async function loadLanguages(orm) {\n    if (!loadLanguages.installedLanguages) {\n        loadLanguages.installedLanguages = await orm.call(\"res.lang\", \"get_installed\");\n    }\n    return loadLanguages.installedLanguages;\n}\n\nfunction _getTranslation(sourceTerm, ctx) {\n    return translatedTerms[ctx]?.[sourceTerm] ?? translatedTermsGlobal[sourceTerm] ?? sourceTerm;\n}\n\n/**\n * Same behavior as sprintf, but doing two additional things:\n * - If any of the provided values is an iterable, it will format its items\n *   as a language-specific formatted string representing the elements of the\n *   list.\n * - If any of the provided values is a markup, it will escape all non-markup\n *   content before performing the interpolation, then wraps the result in a\n *   markup.\n *\n * @param {string} str The string with placeholders (%s) to insert values into.\n * @param  {...any} values Primitive values to insert in place of placeholders.\n * @returns {string|Markup}\n */\nfunction _safeFormatAndSprintf(str, ...values) {\n    let hasMarkup = false;\n    let valuesObject = values;\n    if (values.length === 1 && isObject(values[0])) {\n        valuesObject = values[0];\n    }\n    for (const [key, value] of Object.entries(valuesObject)) {\n        // The `!(value instanceof String)` check is to prevent interpreting `Markup` and `LazyTranslatedString`\n        // objects as iterables, since they are both subclasses of `String`.\n        if (isIterable(value) && !(value instanceof String)) {\n            valuesObject[key] = formatList(value);\n        }\n        hasMarkup ||= value instanceof Markup;\n    }\n    if (hasMarkup) {\n        return htmlSprintf(str, ...values);\n    }\n    return sprintf(str, ...values);\n}\n\n/**\n * This is a wrapper for _t that the transpiler injects in its place\n * to provide the knowledge of the module from which it was called.\n *\n * Providing the context of the module is useful to avoid conflicting\n * translations, e.g. \"table\" has a different meaning depending on the module:\n * the table of a restaurant (POS module) vs. a spreadsheet table.\n *\n * @param {string} str The term to translate\n * @param {string} moduleName The name of the module, used as a context key to\n * retrieve the translation.\n * @param  {...any} args The other arguments passed to _t.\n */\nexport function appTranslateFn(str, moduleName, ...args) {\n    odoo.translationContext = moduleName;\n    const translatedTerm = _t(str, ...args);\n    odoo.translationContext = null;\n    return translatedTerm;\n}\n", "export * from \"@web/core/l10n/utils/format_list\";\nexport * from \"@web/core/l10n/utils/locales\";\nexport * from \"@web/core/l10n/utils/normalize\";\n", "import { user } from \"@web/core/user\";\n\n/**\n * Convert Unicode TR35-49 list pattern types to ES Intl.ListFormat options\n */\nconst LIST_STYLES = {\n    standard: {\n        type: \"conjunction\",\n        style: \"long\",\n    },\n    \"standard-short\": {\n        type: \"conjunction\",\n        style: \"short\",\n    },\n    or: {\n        type: \"disjunction\",\n        style: \"long\",\n    },\n    \"or-short\": {\n        type: \"disjunction\",\n        style: \"short\",\n    },\n    unit: {\n        type: \"unit\",\n        style: \"long\",\n    },\n    \"unit-short\": {\n        type: \"unit\",\n        style: \"short\",\n    },\n    \"unit-narrow\": {\n        type: \"unit\",\n        style: \"narrow\",\n    },\n};\n\n/**\n * Format the items in `list` as a list in a locale-dependent manner with the chosen style.\n *\n * The available styles are defined in the Unicode TR35-49 spec:\n * * standard:\n *   A typical \"and\" list for arbitrary placeholders.\n *   e.g. \"January, February, and March\"\n * * standard-short:\n *   A short version of an \"and\" list, suitable for use with short or abbreviated placeholder values.\n *   e.g. \"Jan., Feb., and Mar.\"\n * * or:\n *   A typical \"or\" list for arbitrary placeholders.\n *   e.g. \"January, February, or March\"\n * * or-short:\n *   A short version of an \"or\" list.\n *   e.g. \"Jan., Feb., or Mar.\"\n * * unit:\n *   A list suitable for wide units.\n *   e.g. \"3 feet, 7 inches\"\n * * unit-short:\n *   A list suitable for short units\n *   e.g. \"3 ft, 7 in\"\n * * unit-narrow:\n *   A list suitable for narrow units, where space on the screen is very limited.\n *   e.g. \"3\u2032 7\u2033\"\n *\n * See https://www.unicode.org/reports/tr35/tr35-49/tr35-general.html#ListPatterns for more details.\n *\n * @param {string[]} list The array of values to format into a list.\n * @param {Object} [param0]\n * @param {string} [param0.localeCode] The locale to use (e.g. en-US).\n * @param {\"standard\"|\"standard-short\"|\"or\"|\"or-short\"|\"unit\"|\"unit-short\"|\"unit-narrow\"} [param0.style=\"standard\"] The style to format the list with.\n * @returns {string} The formatted list.\n */\nexport function formatList(list, { localeCode = \"\", style = \"standard\" } = {}) {\n    const locale = localeCode || user.lang || \"en-US\";\n    const formatter = new Intl.ListFormat(locale, LIST_STYLES[style]);\n    return formatter.format(Array.from(list, String));\n}\n", "/**\n * Converts a locale from JavaScript to Python format.\n *\n * Most of the time the conversion is simply to replace - with _.\n * Example: fr-BE \u2192 fr_BE\n *\n * Exceptions:\n *  - Serbian can be written in both Latin and Cyrillic scripts interchangeably,\n *  therefore its locale includes a special modifier to indicate which script to\n *  use. Example: sr-Latn \u2192 sr@latin\n *  - Tagalog/Filipino: The \"fil\" locale is replaced by \"tl\" for compatibility\n *  with the Python side (where the \"fil\" locale doesn't exist).\n *\n * BCP 47 (JS):\n *  language[-extlang][-script][-region][-variant][-extension][-privateuse]\n *  https://www.ietf.org/rfc/rfc5646.txt\n * XPG syntax (Python):\n *  language[_territory][.codeset][@modifier]\n *  https://www.gnu.org/software/libc/manual/html_node/Locale-Names.html\n *\n * @param {string} locale The locale formatted for use on the JavaScript-side.\n * @returns {string} The locale formatted for use on the Python-side.\n */\nexport function jsToPyLocale(locale) {\n    if (!locale) {\n        return \"\";\n    }\n    try {\n        var { language, script, region } = new Intl.Locale(locale);\n        // new Intl.Locale(\"tl-PH\") produces fil-PH, which one might not expect\n        if (language === \"fil\") {\n            language = \"tl\";\n        }\n    } catch {\n        return locale;\n    }\n    let xpgLocale = language;\n    if (region) {\n        xpgLocale += `_${region}`;\n    }\n    switch (script) {\n        case \"Cyrl\":\n            xpgLocale += \"@Cyrl\";\n            break;\n        case \"Latn\":\n            xpgLocale += \"@latin\";\n            break;\n    }\n    return xpgLocale;\n}\n\n/**\n * Converts a locale from Python to JavaScript format.\n *\n * Most of the time the conversion is simply to replace _ with -.\n * Example: fr_BE \u2192 fr-BE\n *\n * Exception: Serbian can be written in both Latin and Cyrillic scripts\n * interchangeably, therefore its locale includes a special modifier\n * to indicate which script to use.\n * Example: sr@latin \u2192 sr-Latn\n *\n * BCP 47 (JS):\n *  language[-extlang][-script][-region][-variant][-extension][-privateuse]\n *  https://www.ietf.org/rfc/rfc5646.txt\n * XPG syntax (Python):\n *  language[_territory][.codeset][@modifier]\n *  https://www.gnu.org/software/libc/manual/html_node/Locale-Names.html\n *\n * @param {string} locale The locale formatted for use on the Python-side.\n * @returns {string} The locale formatted for use on the JavaScript-side.\n */\nexport function pyToJsLocale(locale) {\n    if (!locale) {\n        return \"\";\n    }\n    const regex = /^([a-z]+)(_[A-Z\\d]+)?(@.+)?$/;\n    const match = locale.match(regex);\n    if (!match) {\n        return locale;\n    }\n    const [, language, territory, modifier] = match;\n    const subtags = [language];\n    switch (modifier) {\n        case \"@Cyrl\":\n            subtags.push(\"Cyrl\");\n            break;\n        case \"@latin\":\n            subtags.push(\"Latn\");\n            break;\n    }\n    if (territory) {\n        subtags.push(territory.slice(1));\n    }\n    return subtags.join(\"-\");\n}\n", "/**\n * Normalizes a string for use in comparison.\n *\n * @example\n * normalize(\"d\u00e9\u00e7\u00fbmes\") === normalize(\"DECUMES\")\n * normalize(\"\ud835\udd16\ud835\udd25\ud835\udd2f\ud835\udd22\ud835\udd28\") === normalize(\"Shrek\")\n * normalize(\"Scle\u00dfin\") === normalize(\"Sclessin\")\n * normalize(\"\u0152dipe\") === normalize(\"OeDiPe\")\n *\n * @param {string} str\n * @returns {string}\n */\nexport function normalize(str) {\n    return casefold(unaccent(expandLigatures(str.normalize(\"NFKC\"))));\n}\n\n/**\n * Searches for \"substr\" in \"src\". The search is performed on normalized strings\n * so that \"ce\" can match \"C\u00e9dric\".\n *\n * @param {string} src\n * @param {string} substr\n * @returns {{match: string, start: number, end: number}}\n */\nexport function normalizedMatch(src, substr) {\n    if (!substr) {\n        return { start: 0, end: 0, match: \"\" };\n    }\n    /**\n     * Array.from splits the string into an array of codepoints. This avoids\n     * processing unpaired surrogates, which could lead to unexpected results.\n     *\n     * \"\ud835\udd16\"[0];              // \"\\ud835\" \u2190 unpaired surrogate!!\n     * Array.from(\"\ud835\udd16\")[0];  // \"\ud835\udd16\"\n     *\n     * \"\ud835\udd16\".split(\"\");   // Array [ \"\\ud835\", \"\\udd16\" ]\n     * Array.from(\"\ud835\udd16\"); // Array [ \"\ud835\udd16\" ]\n     *\n     * \"\ud835\udd16\".split(\"\").map((c) => c.normalize(\"NFKC\")).join(\"\");      // \"\ud835\udd16\"\n     * Array.from(\"\ud835\udd16\").map((c) => c.normalize(\"NFKC\")).join(\"\");    // \"S\"\n     */\n    const srcAsCodepoints = Array.from(src);\n    /**\n     * Instead of calling normalize directly on the source string, the source is\n     * split into an array of codepoints, where each of the elements is\n     * normalized individually. This is because this function is expected to\n     * return the start and end indexes of the match in the *original*,\n     * unnormalized string, but strings can grow in length during normalization,\n     * which would alter the indexes. Now, even if the length of the individual\n     * elements grows, the length of the containing array remains the same.\n     */\n    const normalizedSrc = srcAsCodepoints.map(normalize);\n    const normalizedSubstr = Array.from(normalize(substr));\n    /**\n     * normalizedSrc can contain empty strings if the source is an NFD string,\n     * corresponding to diacritics that have been stripped off. They must be\n     * taken into account in the length calculation to get the indexes right,\n     * hence Math.max(x.length, 1).\n     */\n    const flattenSrcLength = normalizedSrc.reduce((acc, x) => acc + Math.max(x.length, 1), 0);\n    for (let i = 0; i <= flattenSrcLength - normalizedSubstr.length; ++i) {\n        const substrStack = Array.from(normalizedSubstr).reverse();\n        for (let j = 0; i + j < normalizedSrc.length; ++j) {\n            const current = normalizedSrc[i + j];\n            // \"every\" in case normalization expanded current to several chars\n            if (![...current].every((c) => substrStack.length === 0 || c === substrStack.pop())) {\n                break;\n            }\n            if (substrStack.length === 0) {\n                // full substring matched, return the result \ud83d\ude24\n                const start = srcAsCodepoints.slice(0, i).join(\"\").length;\n                const match = srcAsCodepoints.slice(i, i + j + 1).join(\"\");\n                const end = start + match.length;\n                return { start, end, match };\n            }\n        }\n    }\n    return { start: -1, end: -1, match: \"\" };\n}\n\n/**\n * Searches for \"substr\" in \"src\" as is done in normalizedMatch\n * but returns an array of all successful matches\n *\n * @param {string} src\n * @param {string} substr\n * @returns {Array<{match: string, start: number, end: number}>}\n */\nexport function normalizedMatches(src, substr) {\n    const matches = [];\n    let index = 0;\n    while (src.length) {\n        const { start, end, match } = normalizedMatch(src, substr);\n        if (match) {\n            matches.push({ start: index + start, end: index + end, match });\n            index += end;\n            src = src.slice(end);\n        } else {\n            break;\n        }\n    }\n    return matches;\n}\n\nconst DECOMPOSITION_BY_LIGATURE = new Map([\n    [\"\u00c6\", \"Ae\"], // Danish, Norwegian, Icelandic, French (rare)...\n    [\"\u00e6\", \"ae\"],\n    [\"\u0152\", \"Oe\"], // French: \"Richard C\u0153ur de Lion\"\n    [\"\u0153\", \"oe\"],\n    [\"\u0132\", \"IJ\"], // Dutch: \"IJzer\"\n    [\"\u0133\", \"ij\"],\n]);\n\n/**\n * Splits ligatures into their constituent glyphs, e.g. turns \u0152 into Oe.\n *\n * @param {string} str\n * @returns {string}\n */\nfunction expandLigatures(str) {\n    return Array.from(str, (char) => DECOMPOSITION_BY_LIGATURE.get(char) ?? char).join(\"\");\n}\n\n/**\n * Diacritics are marks, such as accents or cedilla, that when added to a letter\n * change its pronunciation or meaning. Unicode has a category for them, but it\n * doesn't consider characters like \"\u00f8\" to be a diacritical \"o\". Below is a list\n * of characters that could be considered \"diacritical characters\" but aren't\n * labeled as such by Unicode.\n */\nconst DIACRITIC_LIKES = new Map([\n    [\"\u00d8\", \"O\"], // notably used in Danish and Norwegian: \"J\u00f8rgen\"\n    [\"\u00f8\", \"o\"],\n    [\"\u0141\", \"L\"], // notably used in Polish: \"Pawe\u0142\"\n    [\"\u0142\", \"l\"],\n    [\"\u00d0\", \"D\"], // Icelandic, \"Borgarfj\u00f6r\u00f0ur\"\n    [\"\u00f0\", \"d\"],\n    [\"\u0126\", \"H\"], // Maltese, \"\u0126amrun Spartans Football Club\"\n    [\"\u0127\", \"h\"],\n    [\"\u0166\", \"T\"], // apparently used in S\u00e1mi languages, very few speakers\n    [\"\u0167\", \"t\"],\n]);\n\n/**\n * Removes \"diacritics\" (funny marks added to letters, such as accents and\n * cedillas) from a string.\n *\n * @param {string} str\n * @returns {string}\n */\nfunction unaccent(str) {\n    return Array.from(\n        str.normalize(\"NFD\").replace(/\\p{Nonspacing_Mark}/gu, \"\"),\n        (char) => DIACRITIC_LIKES.get(char) ?? char\n    ).join(\"\");\n}\n\n/**\n * Normalizes string case for use in comparison.\n *\n * Some characters change length when converted from one case to another. A\n * common example is the German letter \"\u00df,\" which becomes \"SS\" when uppercased.\n * This function ensures that these special cases are handled correctly.\n *\n * \u26a0 Doesn't preserve \"Turkish I\"s.\n *\n * @see https://www.w3.org/TR/charmod-norm/#definitionCaseFolding\n * @see https://www.unicode.org/Public/UNIDATA/CaseFolding.txt\n *\n * @example\n * casefold(\"AAAAAAAA\")                 // \"aaaaaaaa\"\n * casefold(\"\u0587\")                        // \"\u0535\u0552\"\n * casefold(\"Kevin Gro\u00dfkreutz\")         // \"kevin grosskreutz\"\n * casefold(\"Diyarbak\u0131r\")               // \"diyarbakir\"\n * casefold(\"\u00df\") !== \"\u00df\".toLowerCase()  // true\n * casefold(\"\u00df\") === casefold(\"SS\")     // true\n *\n * @param {string} str\n * @returns {string} lowercase string after \"full case folding\"\n */\nfunction casefold(str) {\n    return str.toLowerCase().toUpperCase().toLowerCase();\n}\n", "import { isVisible } from \"@web/core/utils/ui\";\nimport { delay } from \"@web/core/utils/concurrency\";\nimport { validate } from \"@odoo/owl\";\n\nconst macroSchema = {\n    name: { type: String, optional: true },\n    timeout: { type: Number, optional: true },\n    steps: {\n        type: Array,\n        element: {\n            type: Object,\n            shape: {\n                action: { type: [Function, String], optional: true },\n                timeout: { type: Number, optional: true },\n                trigger: { type: [Function, String], optional: true },\n            },\n            validate: (step) => step.action || step.trigger,\n        },\n    },\n    onComplete: { type: Function, optional: true },\n    onStep: { type: Function, optional: true },\n    onError: { type: Function, optional: true },\n};\n\nclass MacroError extends Error {\n    constructor(type, message, options) {\n        super(message, options);\n        this.type = type;\n    }\n}\n\nasync function performAction(trigger, action) {\n    if (!action) {\n        return;\n    }\n    try {\n        return await action(trigger);\n    } catch (error) {\n        throw new MacroError(\n            \"Action\",\n            error.stack || `ERROR during perform action: ${error.message}`,\n            {cause: error}\n        );\n    }\n}\n\nasync function waitForTrigger(trigger) {\n    if (!trigger) {\n        return;\n    }\n    try {\n        await delay(50);\n        return await waitUntil(() => {\n            if (typeof trigger === \"function\") {\n                return trigger();\n            } else if (typeof trigger === \"string\") {\n                const triggerEl = document.querySelector(trigger);\n                return isVisible(triggerEl) && triggerEl;\n            }\n        });\n    } catch (error) {\n        throw new MacroError(\"Trigger\", `ERROR during find trigger:\\n${error.message}`, {\n            cause: error,\n        });\n    }\n}\n\nexport async function waitUntil(predicate) {\n    const result = predicate();\n    if (result) {\n        return Promise.resolve(result);\n    }\n    let handle;\n    return new Promise((resolve) => {\n        const runCheck = () => {\n            const result = predicate();\n            if (result) {\n                resolve(result);\n            }\n            handle = requestAnimationFrame(runCheck);\n        };\n        handle = requestAnimationFrame(runCheck);\n    }).finally(() => {\n        cancelAnimationFrame(handle);\n    });\n}\n\nexport class Macro {\n    currentIndex = 0;\n    isComplete = false;\n    constructor(descr) {\n        try {\n            validate(descr, macroSchema);\n        } catch (error) {\n            throw new Error(\n                `Error in schema for Macro ${JSON.stringify(descr, null, 4)}\\n${error.message}`\n            );\n        }\n        Object.assign(this, descr);\n        this.onComplete = this.onComplete || (() => {});\n        this.onStep = this.onStep || (() => {});\n        this.onError =\n            this.onError ||\n            ((error, step, index) => {\n                console.error(error.message, step, index);\n            });\n    }\n\n    async start() {\n        await this.advance();\n    }\n\n    async advance() {\n        if (this.isComplete || this.currentIndex >= this.steps.length) {\n            this.stop();\n            return;\n        }\n        try {\n            const step = this.steps[this.currentIndex];\n            const timeoutDelay = step.timeout || this.timeout || 10000;\n            const executeStep = async () => {\n                const trigger = await waitForTrigger(step.trigger);\n                const result = await performAction(trigger, step.action);\n                await this.onStep({ step, trigger, index: this.currentIndex });\n                return result;\n            };\n            const launchTimer = async () => {\n                await delay(timeoutDelay);\n                throw new MacroError(\n                    \"Timeout\",\n                    `TIMEOUT step failed to complete within ${timeoutDelay} ms.`\n                );\n            };\n            // If falsy action result, it means the action worked properly.\n            // So we can proceed to the next step.\n            const actionResult = await Promise.race([executeStep(), launchTimer()]);\n            if (actionResult) {\n                this.stop();\n                return;\n            }\n        } catch (error) {\n            this.stop(error);\n            return;\n        }\n        this.currentIndex++;\n        await this.advance();\n    }\n\n    stop(error) {\n        if (this.isComplete) {\n            return;\n        }\n        this.isComplete = true;\n        if (error) {\n            const step = this.steps[this.currentIndex];\n            this.onError({ error, step, index: this.currentIndex });\n        } else if (this.currentIndex === this.steps.length) {\n            this.onComplete();\n        }\n    }\n}\n\nexport class MacroMutationObserver {\n    observerOptions = {\n        attributes: true,\n        childList: true,\n        subtree: true,\n        characterData: true,\n    };\n    constructor(callback) {\n        this.callback = callback;\n        this.observer = new MutationObserver((mutationList, observer) => {\n            callback(mutationList);\n            mutationList.forEach((mutationRecord) =>\n                Array.from(mutationRecord.addedNodes).forEach((node) => {\n                    let iframes = [];\n                    if (String(node.tagName).toLowerCase() === \"iframe\") {\n                        iframes = [node];\n                    } else if (node instanceof HTMLElement) {\n                        iframes = Array.from(node.querySelectorAll(\"iframe\"));\n                    }\n                    iframes.forEach((iframeEl) =>\n                        this.observeIframe(iframeEl, observer, () => callback())\n                    );\n                    this.findAllShadowRoots(node).forEach((shadowRoot) =>\n                        observer.observe(shadowRoot, this.observerOptions)\n                    );\n                })\n            );\n        });\n    }\n    disconnect() {\n        this.observer.disconnect();\n    }\n    findAllShadowRoots(node, shadowRoots = []) {\n        if (node.shadowRoot) {\n            shadowRoots.push(node.shadowRoot);\n            this.findAllShadowRoots(node.shadowRoot, shadowRoots);\n        }\n        node.childNodes.forEach((child) => {\n            this.findAllShadowRoots(child, shadowRoots);\n        });\n        return shadowRoots;\n    }\n    observe(target) {\n        this.observer.observe(target, this.observerOptions);\n        //When iframes already exist at \"this.target\" initialization\n        target\n            .querySelectorAll(\"iframe\")\n            .forEach((el) => this.observeIframe(el, this.observer, () => this.callback()));\n        //When shadowDom already exist at \"this.target\" initialization\n        this.findAllShadowRoots(target).forEach((shadowRoot) => {\n            this.observer.observe(shadowRoot, this.observerOptions);\n        });\n    }\n    observeIframe(iframeEl, observer, callback) {\n        const observerOptions = {\n            attributes: true,\n            childList: true,\n            subtree: true,\n            characterData: true,\n        };\n        const observeIframeContent = () => {\n            if (iframeEl.contentDocument) {\n                iframeEl.contentDocument.addEventListener(\"load\", (event) => {\n                    callback();\n                    observer.observe(event.target, observerOptions);\n                });\n                if (!iframeEl.src || iframeEl.contentDocument.readyState === \"complete\") {\n                    callback();\n                    observer.observe(iframeEl.contentDocument, observerOptions);\n                }\n            }\n        };\n        observeIframeContent();\n        iframeEl.addEventListener(\"load\", observeIframeContent);\n    }\n}\n", "import { Component, xml } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\nimport { useRegistry } from \"@web/core/registry_hook\";\nimport { ErrorHandler } from \"@web/core/utils/components\";\n\nconst mainComponents = registry.category(\"main_components\");\n\nmainComponents.addValidation({\n    Component: { validate: (c) => c.prototype instanceof Component },\n    props: { type: Object, optional: true }\n});\n\nexport class MainComponentsContainer extends Component {\n    static components = { ErrorHandler };\n    static props = {};\n    static template = xml`\n    <div class=\"o-main-components-container\">\n        <t t-foreach=\"Components.entries\" t-as=\"C\" t-key=\"C[0]\">\n            <ErrorHandler onError=\"error => this.handleComponentError(error, C)\">\n                <t t-component=\"C[1].Component\" t-props=\"C[1].props\"/>\n            </ErrorHandler>\n        </t>\n    </div>\n    `;\n\n    setup() {\n        this.Components = useRegistry(mainComponents);\n    }\n\n    handleComponentError(error, C) {\n        // remove the faulty component and rerender without it\n        this.Components.entries.splice(this.Components.entries.indexOf(C), 1);\n        this.render();\n        /**\n         * we rethrow the error to notify the user something bad happened.\n         * We do it after a tick to make sure owl can properly finish its\n         * rendering\n         */\n        Promise.resolve().then(() => {\n            throw error;\n        });\n    }\n}\n", "import { Component, onWillStart, onWillUpdateProps, useState } from \"@odoo/owl\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { ModelFieldSelectorPopover } from \"./model_field_selector_popover\";\n\nexport class ModelFieldSelector extends Component {\n    static template = \"web._ModelFieldSelector\";\n    static components = {\n        Popover: ModelFieldSelectorPopover,\n    };\n    static props = {\n        resModel: String,\n        path: { optional: true },\n        allowEmpty: { type: Boolean, optional: true },\n        readonly: { type: Boolean, optional: true },\n        readProperty: { type: Boolean, optional: true },\n        showSearchInput: { type: Boolean, optional: true },\n        isDebugMode: { type: Boolean, optional: true },\n        update: { type: Function, optional: true },\n        filter: { type: Function, optional: true },\n        sort: { type: Function, optional: true },\n        followRelations: { type: Boolean, optional: true },\n        showDebugInput: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        readonly: true,\n        allowEmpty: false,\n        isDebugMode: false,\n        showSearchInput: true,\n        update: () => {},\n        followRelations: true,\n    };\n\n    setup() {\n        this.fieldService = useService(\"field\");\n        this.popover = usePopover(this.constructor.components.Popover, {\n            popoverClass: \"o_popover_field_selector\",\n            onClose: async () => {\n                if (this.newPath !== null) {\n                    const fieldInfo = await this.fieldService.loadFieldInfo(\n                        this.props.resModel,\n                        this.newPath\n                    );\n                    this.props.update(this.newPath, fieldInfo);\n                }\n            },\n        });\n        this.keepLast = new KeepLast();\n        this.state = useState({ isInvalid: false, displayNames: [] });\n        onWillStart(() => this.updateState(this.props));\n        onWillUpdateProps((nextProps) => this.updateState(nextProps));\n    }\n\n    openPopover(currentTarget) {\n        if (this.props.readonly) {\n            return;\n        }\n        this.newPath = null;\n        this.popover.open(currentTarget, {\n            resModel: this.props.resModel,\n            path: this.props.path,\n            readProperty: this.props.readProperty,\n            update: (path, _fieldInfo, debug = false) => {\n                this.newPath = path;\n                if (!debug) {\n                    this.updateState({ ...this.props, path }, true);\n                }\n            },\n            showSearchInput: this.props.showSearchInput,\n            isDebugMode: this.props.isDebugMode,\n            filter: this.props.filter,\n            sort: this.props.sort,\n            followRelations: this.props.followRelations,\n            showDebugInput: this.props.showDebugInput,\n        });\n    }\n\n    async updateState(params, isConcurrent) {\n        const { resModel, path, allowEmpty } = params;\n        let prom = this.fieldService.loadPathDescription(resModel, path, allowEmpty);\n        if (isConcurrent) {\n            prom = this.keepLast.add(prom);\n        }\n        const state = await prom;\n        Object.assign(this.state, state);\n    }\n\n    clear() {\n        if (this.popover.isOpen) {\n            this.newPath = \"\";\n            this.popover.close();\n            return;\n        }\n        this.props.update(\"\", { resModel: this.props.resModel, fieldDef: null });\n    }\n}\n", "import { Component, onWillStart, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sortBy } from \"@web/core/utils/arrays\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { fuzzyLookup } from \"@web/core/utils/search\";\nimport { debounce } from \"@web/core/utils/timing\";\n\nclass Page {\n    constructor(resModel, fieldDefs, options = {}) {\n        this.resModel = resModel;\n        this.fieldDefs = fieldDefs;\n        const {\n            previousPage = null,\n            selectedName = null,\n            isDebugMode,\n            readProperty = false,\n            sortFn = (fieldDefs) => sortBy(Object.keys(fieldDefs), (key) => fieldDefs[key].string),\n        } = options;\n        this.previousPage = previousPage;\n        this.selectedName = selectedName;\n        this.isDebugMode = isDebugMode;\n        this.readProperty = readProperty;\n        this.sortedFieldNames = sortFn(fieldDefs);\n        this.fieldNames = this.sortedFieldNames;\n        this.query = \"\";\n        this.focusedFieldName = null;\n        this.resetFocusedFieldName();\n    }\n\n    get path() {\n        const previousPath = this.previousPage?.path || \"\";\n        const name = this.selectedName;\n\n        if (this.readProperty && this.selectedField && this.selectedField.is_property) {\n            if (this.selectedField.relation) {\n                return `${previousPath}.get('${name}', env['${this.selectedField.relation}'])`;\n            }\n            return `${previousPath}.get('${name}')`;\n        }\n        if (name) {\n            if (previousPath) {\n                return `${previousPath}.${name}`;\n            }\n            return name;\n        }\n        return previousPath;\n    }\n\n    get selectedField() {\n        return this.fieldDefs[this.selectedName];\n    }\n\n    get title() {\n        const prefix = this.previousPage?.previousPage ? \"... > \" : \"\";\n        const title = this.previousPage?.selectedField?.string || \"\";\n        if (prefix.length || title.length) {\n            return `${prefix}${title}`;\n        }\n        return _t(\"Select a field\");\n    }\n\n    focus(direction) {\n        if (!this.fieldNames.length) {\n            return;\n        }\n        const index = this.fieldNames.indexOf(this.focusedFieldName);\n        if (direction === \"previous\") {\n            if (index === 0) {\n                this.focusedFieldName = this.fieldNames[this.fieldNames.length - 1];\n            } else {\n                this.focusedFieldName = this.fieldNames[index - 1];\n            }\n        } else {\n            if (index === this.fieldNames.length - 1) {\n                this.focusedFieldName = this.fieldNames[0];\n            } else {\n                this.focusedFieldName = this.fieldNames[index + 1];\n            }\n        }\n    }\n\n    resetFocusedFieldName() {\n        if (this.selectedName && this.fieldNames.includes(this.selectedName)) {\n            this.focusedFieldName = this.selectedName;\n        } else {\n            this.focusedFieldName = this.fieldNames.length ? this.fieldNames[0] : null;\n        }\n    }\n\n    searchFields(query = \"\") {\n        this.query = query;\n        this.fieldNames = this.sortedFieldNames;\n        if (query) {\n            this.fieldNames = fuzzyLookup(query, this.fieldNames, (key) => {\n                const vals = [this.fieldDefs[key].string];\n                if (this.isDebugMode) {\n                    vals.push(key);\n                }\n                return vals;\n            });\n        }\n        this.resetFocusedFieldName();\n    }\n}\n\nexport class ModelFieldSelectorPopover extends Component {\n    static template = \"web.ModelFieldSelectorPopover\";\n    static props = {\n        close: Function,\n        filter: { type: Function, optional: true },\n        sort: { type: Function, optional: true },\n        followRelations: { type: Boolean, optional: true },\n        showDebugInput: { type: Boolean, optional: true },\n        isDebugMode: { type: Boolean, optional: true },\n        path: { optional: true },\n        readProperty: { type: Boolean, optional: true },\n        resModel: String,\n        showSearchInput: { type: Boolean, optional: true },\n        update: Function,\n    };\n    static defaultProps = {\n        filter: (value) => value.searchable && value.type != \"json\" && value.type !== \"separator\",\n        isDebugMode: false,\n        followRelations: true,\n    };\n\n    setup() {\n        this.fieldService = useService(\"field\");\n        this.state = useState({ page: null });\n        this.keepLast = new KeepLast();\n        this.debouncedSearchFields = debounce(this.searchFields.bind(this), 250);\n\n        onWillStart(async () => {\n            this.state.page = await this.loadPages(this.props.resModel, this.props.path);\n        });\n\n        const rootRef = useRef(\"root\");\n        useEffect(() => {\n            const focusedElement = rootRef.el.querySelector(\n                \".o_model_field_selector_popover_item.active\"\n            );\n            if (focusedElement) {\n                // current page can be empty (e.g. after a search)\n                focusedElement.scrollIntoView({ block: \"center\" });\n            }\n        });\n        useEffect(\n            () => {\n                if (this.props.showSearchInput) {\n                    const searchInput = rootRef.el.querySelector(\n                        \".o_model_field_selector_popover_search .o_input\"\n                    );\n                    searchInput.focus();\n                }\n            },\n            () => [this.state.page]\n        );\n    }\n\n    get fieldNames() {\n        return this.state.page.fieldNames;\n    }\n\n    get showDebugInput() {\n        return this.props.showDebugInput ?? this.props.isDebugMode;\n    }\n\n    canFollowRelationFor(fieldDef) {\n        if (fieldDef.type === \"properties\") {\n            return true;\n        }\n        if (!this.props.followRelations) {\n            return false;\n        }\n        return fieldDef.relation;\n    }\n\n    filter(fieldDefs, path, resModel) {\n        const filteredKeys = Object.keys(fieldDefs).filter((k) =>\n            this.props.filter(fieldDefs[k], path, resModel)\n        );\n        return Object.fromEntries(filteredKeys.map((k) => [k, fieldDefs[k]]));\n    }\n\n    async followRelation(fieldDef) {\n        const { modelsInfo } = await this.keepLast.add(\n            this.fieldService.loadPath(\n                fieldDef.relation || this.state.page.resModel,\n                `${fieldDef.name}.*`\n            )\n        );\n        this.state.page.selectedName = fieldDef.name;\n        const { resModel, fieldDefs } = modelsInfo.at(-1);\n        this.openPage(\n            new Page(resModel, this.filter(fieldDefs, this.state.page.path, resModel), {\n                previousPage: this.state.page,\n                isDebugMode: this.props.isDebugMode,\n                readProperty: this.props.readProperty,\n                sortFn: this.props.sort,\n            })\n        );\n    }\n\n    goToPreviousPage() {\n        this.keepLast.add(Promise.resolve());\n        this.openPage(this.state.page.previousPage);\n    }\n\n    async loadNewPath(path) {\n        const newPage = await this.keepLast.add(this.loadPages(this.props.resModel, path));\n        this.openPage(newPage);\n    }\n\n    async loadPages(resModel, path) {\n        if (typeof path !== \"string\" || !path.length) {\n            const fieldDefs = await this.fieldService.loadFields(resModel);\n            return new Page(resModel, this.filter(fieldDefs, path, resModel), {\n                isDebugMode: this.props.isDebugMode,\n                readProperty: this.props.readProperty,\n                sortFn: this.props.sort,\n            });\n        }\n        const { isInvalid, modelsInfo, names } = await this.fieldService.loadPath(resModel, path);\n        switch (isInvalid) {\n            case \"model\":\n                throw new Error(`Invalid model name: ${resModel}`);\n            case \"path\": {\n                const { resModel, fieldDefs } = modelsInfo[0];\n                return new Page(resModel, this.filter(fieldDefs, path, resModel), {\n                    selectedName: path,\n                    isDebugMode: this.props.isDebugMode,\n                    readProperty: this.props.readProperty,\n                    sortFn: this.props.sort,\n                });\n            }\n            default: {\n                let page = null;\n                for (let index = 0; index < names.length; index++) {\n                    const name = names[index];\n                    const { resModel, fieldDefs } = modelsInfo[index];\n                    page = new Page(resModel, this.filter(fieldDefs, path, resModel), {\n                        previousPage: page,\n                        selectedName: name,\n                        isDebugMode: this.props.isDebugMode,\n                        readProperty: this.props.readProperty,\n                        sortFn: this.props.sort,\n                    });\n                }\n                return page;\n            }\n        }\n    }\n\n    openPage(page) {\n        this.state.page = page;\n        this.state.page.searchFields();\n        this.props.update(page.path);\n    }\n\n    searchFields(query) {\n        this.state.page.searchFields(query);\n    }\n\n    selectField(field) {\n        if (field.type === \"properties\") {\n            return this.followRelation(field);\n        }\n        this.keepLast.add(Promise.resolve());\n        this.state.page.selectedName = field.name;\n        this.props.update(this.state.page.path, field);\n        this.props.close(true);\n    }\n\n    onDebugInputKeydown(ev) {\n        switch (ev.key) {\n            case \"Enter\": {\n                ev.preventDefault();\n                ev.stopPropagation();\n                this.loadNewPath(ev.currentTarget.value);\n                break;\n            }\n        }\n    }\n\n    // @TODO should rework/improve this and maybe use hotkeys\n    async onInputKeydown(ev) {\n        const { page } = this.state;\n        switch (ev.key) {\n            case \"ArrowUp\": {\n                if (ev.target.selectionStart === 0) {\n                    page.focus(\"previous\");\n                }\n                break;\n            }\n            case \"ArrowDown\": {\n                if (ev.target.selectionStart === page.query.length) {\n                    page.focus(\"next\");\n                }\n                break;\n            }\n            case \"ArrowLeft\": {\n                if (ev.target.selectionStart === 0 && page.previousPage) {\n                    this.goToPreviousPage();\n                }\n                break;\n            }\n            case \"ArrowRight\": {\n                if (ev.target.selectionStart === page.query.length) {\n                    const focusedFieldName = this.state.page.focusedFieldName;\n                    if (focusedFieldName) {\n                        const fieldDef = this.state.page.fieldDefs[focusedFieldName];\n                        if (this.canFollowRelationFor(fieldDef)) {\n                            this.followRelation(fieldDef);\n                        }\n                    }\n                }\n                break;\n            }\n            case \"Enter\": {\n                const focusedFieldName = this.state.page.focusedFieldName;\n                if (focusedFieldName) {\n                    const fieldDef = this.state.page.fieldDefs[focusedFieldName];\n                    this.selectField(fieldDef);\n                } else {\n                    ev.preventDefault();\n                    ev.stopPropagation();\n                }\n                break;\n            }\n            case \"Escape\": {\n                ev.preventDefault();\n                ev.stopPropagation();\n                this.props.close();\n                break;\n            }\n        }\n    }\n}\n", "import { AutoComplete } from \"@web/core/autocomplete/autocomplete\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { fuzzyLookup } from \"@web/core/utils/search\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nimport { Component, onWillStart } from \"@odoo/owl\";\n\nexport class ModelSelector extends Component {\n    static template = \"web.ModelSelector\";\n    static components = { AutoComplete };\n    static props = {\n        onModelSelected: Function,\n        id: { type: String, optional: true },\n        value: { type: String, optional: true },\n        placeholder: { type: String, optional: true },\n        // list of models technical name, if not set\n        // we will fetch all models we have access to\n        models: { type: Array, optional: true },\n        nbVisibleModels: { type: Number, optional: true },\n        autofocus: { type: Boolean, optional: true },\n    };\n\n    setup() {\n        this.orm = useService(\"orm\");\n\n        onWillStart(async () => {\n            if (!this.props.models) {\n                this.models = await this._fetchAvailableModels();\n            } else {\n                this.models = await this.orm.call(\"ir.model\", \"display_name_for\", [\n                    this.props.models,\n                ]);\n            }\n\n            this.models = this.models.map((record) => ({\n                cssClass: `o_model_selector_${record.model.replaceAll(\".\", \"_\")}`,\n                data: {\n                    technical: record.model,\n                },\n                label: record.display_name,\n                onSelect: () =>\n                    this.props.onModelSelected({\n                        label: record.display_name,\n                        technical: record.model,\n                    }),\n            }));\n        });\n    }\n\n    get sources() {\n        return [this.optionsSource];\n    }\n\n    get placeholder() {\n        return this.props.placeholder || _t(\"Type a model here...\");\n    }\n\n    get optionsSource() {\n        return {\n            placeholder: _t(\"Loading...\"),\n            options: this.loadOptionsSource.bind(this),\n        };\n    }\n\n    get nbVisibleModels() {\n        return this.props.nbVisibleModels || 8;\n    }\n\n    filterModels(name) {\n        if (!name) {\n            const visibleModels = this.models.slice(0, this.nbVisibleModels);\n            if (this.models.length - visibleModels.length > 0) {\n                visibleModels.push({\n                    label: _t(\"Start typing...\"),\n                    cssClass: \"o_m2o_start_typing\",\n                });\n            }\n            return visibleModels;\n        }\n        return fuzzyLookup(name, this.models, (model) => model.data.technical + model.label);\n    }\n\n    loadOptionsSource(request) {\n        const options = this.filterModels(request);\n\n        if (!options.length) {\n            options.push({\n                label: _t(\"No records\"),\n                cssClass: \"o_m2o_no_result\",\n            });\n        }\n        return options;\n    }\n\n    /**\n     * Fetch the list of the models that can be\n     * selected for the relational properties.\n     */\n    async _fetchAvailableModels() {\n        const result = await this.orm.call(\"ir.model\", \"get_available_models\");\n        return result || [];\n    }\n}\n", "import { registry } from \"@web/core/registry\";\nimport { unique, zip } from \"@web/core/utils/arrays\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\n\nexport const ERROR_INACCESSIBLE_OR_MISSING = Symbol(\"INACCESSIBLE OR MISSING RECORD ID\");\n\nfunction isId(val) {\n    return Number.isInteger(val) && val >= 1;\n}\n\n/**\n * @typedef {Record<string, (string|ERROR_INACCESSIBLE_OR_MISSING)>} DisplayNames\n */\n\nexport const nameService = {\n    dependencies: [\"orm\"],\n    async: [\"loadDisplayNames\"],\n    start(env, { orm }) {\n        let cache = {};\n        const batches = {};\n\n        function clearCache() {\n            cache = {};\n        }\n\n        env.bus.addEventListener(\"ACTION_MANAGER:UPDATE\", clearCache);\n\n        function getMapping(resModel) {\n            if (!cache[resModel]) {\n                cache[resModel] = {};\n            }\n            return cache[resModel];\n        }\n\n        /**\n         * @param {string} resModel valid resModel name\n         * @param {DisplayNames} displayNames\n         */\n        function addDisplayNames(resModel, displayNames) {\n            const mapping = getMapping(resModel);\n            for (const resId in displayNames) {\n                mapping[resId] = new Deferred();\n                mapping[resId].resolve(displayNames[resId]);\n            }\n        }\n\n        /**\n         * @param {string} resModel valid resModel name\n         * @param {number[]} resIds valid ids\n         * @returns {Promise<DisplayNames>}\n         */\n        async function loadDisplayNames(resModel, resIds) {\n            const mapping = getMapping(resModel);\n            const proms = [];\n            const resIdsToFetch = [];\n            for (const resId of unique(resIds)) {\n                if (!isId(resId)) {\n                    throw new Error(`Invalid ID: ${resId}`);\n                }\n                if (!(resId in mapping)) {\n                    mapping[resId] = new Deferred();\n                    resIdsToFetch.push(resId);\n                }\n                proms.push(mapping[resId]);\n            }\n            if (resIdsToFetch.length) {\n                if (batches[resModel]) {\n                    batches[resModel].push(...resIdsToFetch);\n                } else {\n                    batches[resModel] = resIdsToFetch;\n                    await Promise.resolve();\n                    const idsInBatch = unique(batches[resModel]);\n                    delete batches[resModel];\n\n                    const specification = { display_name: {} };\n                    orm.silent\n                        .webSearchRead(resModel, [[\"id\", \"in\", idsInBatch]], {\n                            specification,\n                            context: { active_test: false },\n                        })\n                        .then(({ records }) => {\n                            const displayNames = Object.fromEntries(\n                                records.map((rec) => [rec.id, rec.display_name])\n                            );\n                            for (const resId of idsInBatch) {\n                                mapping[resId].resolve(\n                                    resId in displayNames\n                                        ? displayNames[resId]\n                                        : ERROR_INACCESSIBLE_OR_MISSING\n                                );\n                            }\n                        });\n                }\n            }\n            const names = await Promise.all(proms);\n            return Object.fromEntries(zip(resIds, names));\n        }\n\n        return { addDisplayNames, clearCache, loadDisplayNames };\n    },\n};\n\nregistry.category(\"services\").add(\"name\", nameService);\n", "import { onWillUnmount, useEffect, useExternalListener, useRef } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { deepMerge } from \"@web/core/utils/objects\";\nimport { scrollTo } from \"@web/core/utils/scrolling\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { browser } from \"@web/core/browser/browser\";\n\nexport const ACTIVE_ELEMENT_CLASS = \"focus\";\nconst throttledFocus = throttleForAnimation((el) => el?.focus());\n\nclass NavigationItem {\n    /**@type {number} */\n    index = -1;\n\n    /**\n     * The container element\n     * @type {Element}\n     */\n    el = undefined;\n\n    /**\n     * The actual \"clicked\" element, it can be the same\n     * as @see el but will be the closest child input if\n     * options.shouldFocusChildInput is true\n     * @type {Element}\n     */\n    target = undefined;\n\n    constructor({ index, el, options, navigator }) {\n        this.index = index;\n\n        /**@private */\n        this._options = options;\n\n        /**\n         * @private\n         * @type {Navigator}\n         */\n        this._navigator = navigator;\n\n        this.el = el;\n        if (this._options.shouldFocusChildInput) {\n            const subInput = el.querySelector(\":scope input, :scope button, :scope textarea\");\n            this.target = subInput || el;\n        } else {\n            this.target = el;\n        }\n\n        if (this.el.ariaSelected !== true) {\n            this.el.ariaSelected = false;\n        }\n\n        const onFocus = () => this.setActive(false);\n        const onMouseMove = () => this._onMouseMove();\n\n        this.target.addEventListener(\"focus\", onFocus);\n        this.target.addEventListener(\"mousemove\", onMouseMove);\n\n        /**@private*/\n        this._removeListeners = () => {\n            this.target.removeEventListener(\"focus\", onFocus);\n            this.target.removeEventListener(\"mousemove\", onMouseMove);\n        };\n    }\n\n    select() {\n        this.setActive();\n        this.target.click();\n    }\n\n    setActive(focus = true) {\n        scrollTo(this.target);\n        this._navigator._setActiveItem(this.index);\n        this.target.classList.add(ACTIVE_ELEMENT_CLASS);\n        this.target.ariaSelected = true;\n\n        if (focus && !this._options.virtualFocus) {\n            throttledFocus.cancel();\n            throttledFocus(this.target);\n        }\n    }\n\n    setInactive(blur = true) {\n        this.target.classList.remove(ACTIVE_ELEMENT_CLASS);\n        this.target.ariaSelected = false;\n        if (blur && !this._options.virtualFocus) {\n            this.target.blur();\n        }\n    }\n\n    /**\n     * @private\n     */\n    _onMouseMove() {\n        if (\n            this._navigator.activeItem !== this &&\n            this._navigator._isNavigationAvailable(this.target)\n        ) {\n            this.setActive(false);\n            this._options.onMouseEnter?.(this);\n        }\n    }\n}\n\nexport class Navigator {\n    /**@type {NavigationItem|undefined}*/\n    activeItem = undefined;\n\n    /**@type {number}*/\n    activeItemIndex = -1;\n\n    /**@type {Array<NavigationItem>}*/\n    items = [];\n\n    /**@private*/ _hotkeyRemoves = [];\n    /**@private*/ _hotkeyService = undefined;\n\n    /**\n     * @param {NavigationOptions} options\n     * @param {import(\"@web/core/hotkeys/hotkey_service\").HotkeyService} hotkeyService\n     */\n    constructor(options, hotkeyService) {\n        this._hotkeyService = hotkeyService;\n\n        /**@private*/\n        this._options = deepMerge(\n            {\n                isNavigationAvailable: ({ target }) =>\n                    this.contains(target) && (this.isFocused || this._options.virtualFocus),\n                shouldFocusChildInput: true,\n                shouldFocusFirstItem: false,\n                shouldRegisterHotkeys: true,\n                virtualFocus: false,\n                hotkeys: {\n                    home: () => this.items[0]?.setActive(),\n                    end: () => this.items.at(-1)?.setActive(),\n                    tab: {\n                        callback: () => this.next(),\n                        bypassEditableProtection: true,\n                    },\n                    \"shift+tab\": {\n                        callback: () => this.previous(),\n                        bypassEditableProtection: true,\n                    },\n                    arrowdown: {\n                        callback: () => this.next(),\n                        bypassEditableProtection: true,\n                    },\n                    arrowup: {\n                        callback: () => this.previous(),\n                        bypassEditableProtection: true,\n                    },\n                    enter: {\n                        isAvailable: ({ navigator }) => Boolean(navigator.activeItem),\n                        callback: () => {\n                            const item = this.activeItem || this.items[0];\n                            item?.select();\n                        },\n                        bypassEditableProtection: true,\n                    },\n                },\n            },\n            options\n        );\n\n        if (this._options.shouldRegisterHotkeys) {\n            this.registerHotkeys();\n        }\n    }\n\n    /**\n     * Returns true if the current active item is not null and still inside the DOM\n     * @type {boolean}\n     */\n    get hasActiveItem() {\n        return Boolean(this.activeItem?.el.isConnected);\n    }\n\n    /**\n     * Returns true if the focus is on any of the navigable items\n     * @type {boolean}\n     */\n    get isFocused() {\n        return this.items.some((item) => item.target.contains(document.activeElement));\n    }\n\n    next() {\n        if (!this.hasActiveItem) {\n            this.items[0]?.setActive();\n        } else {\n            this.items[(this.activeItemIndex + 1) % this.items.length]?.setActive();\n        }\n    }\n\n    previous() {\n        const index = this.activeItemIndex - 1;\n        if (!this.hasActiveItem || index < 0) {\n            this.items.at(-1)?.setActive();\n        } else {\n            this.items[index % this.items.length]?.setActive();\n        }\n    }\n\n    update() {\n        const oldItems = new Map(this.items.map((item) => [item.el, item]));\n        const oldActiveItem = this.activeItem;\n        const elements = this._options.getItems();\n        this.items = [];\n\n        let didUpdate = elements.length !== oldItems.size;\n        for (let index = 0; index < elements.length; index++) {\n            const element = elements[index];\n\n            let item = oldItems.get(element);\n            if (item) {\n                if (item.index !== index) {\n                    item.index = index;\n                    didUpdate = true;\n                }\n                oldItems.delete(element);\n            } else {\n                didUpdate = true;\n                item = new NavigationItem({\n                    index,\n                    el: element,\n                    options: this._options,\n                    navigator: this,\n                });\n            }\n            this.items.push(item);\n        }\n\n        for (const item of oldItems.values()) {\n            item._removeListeners();\n        }\n\n        if (didUpdate) {\n            const activeItemIndex =\n                oldActiveItem && oldActiveItem.el.isConnected\n                    ? this.items.findIndex((item) => item.el === oldActiveItem.el)\n                    : -1;\n            const focusedElementIndex = this.items.findIndex((item) => item.el === document.activeElement);\n            if (activeItemIndex > -1) {\n                this._updateActiveItemIndex(activeItemIndex);\n            } else if (this.activeItemIndex >= 0) {\n                const closest = Math.min(this.activeItemIndex, elements.length - 1);\n                this._updateActiveItemIndex(closest);\n            } else if (focusedElementIndex >= 0) {\n                this._updateActiveItemIndex(focusedElementIndex);\n            } else {\n                this._updateActiveItemIndex(-1);\n            }\n\n            this._options.onUpdated?.(this);\n\n            if (this._options.shouldFocusFirstItem) {\n                this.items[0]?.setActive();\n            }\n        }\n    }\n\n    /**\n     * @param {HTMLElement} target\n     * @returns {boolean}\n     */\n    contains(target) {\n        return this.items.some((item) => item.target.contains(target));\n    }\n\n    registerHotkeys() {\n        if (this._hotkeyRemoves.length > 0) {\n            return;\n        }\n\n        for (const [hotkey, hotkeyInfo] of Object.entries(this._options.hotkeys)) {\n            if (!hotkeyInfo) {\n                continue;\n            }\n\n            const callback = typeof hotkeyInfo == \"function\" ? hotkeyInfo : hotkeyInfo.callback;\n            if (!callback) {\n                continue;\n            }\n\n            const isAvailable = hotkeyInfo?.isAvailable ?? (() => true);\n            const bypassEditableProtection = hotkeyInfo?.bypassEditableProtection ?? false;\n            const allowRepeat = hotkeyInfo?.allowRepeat ?? true;\n\n            this._hotkeyRemoves.push(\n                this._hotkeyService.add(hotkey, async () => await callback(this), {\n                    global: true,\n                    allowRepeat,\n                    isAvailable: (target) =>\n                        this._isNavigationAvailable(target) &&\n                        isAvailable({ navigator: this, target }),\n                    bypassEditableProtection,\n                })\n            );\n        }\n    }\n\n    unregisterHotkeys() {\n        for (const removeHotkey of this._hotkeyRemoves) {\n            removeHotkey();\n        }\n        this._hotkeyRemoves = [];\n    }\n\n    /**\n     * @private\n     */\n    _destroy() {\n        for (const item of this.items) {\n            item._removeListeners();\n        }\n        this.items = [];\n        this.unregisterHotkeys();\n    }\n\n    /**\n     * @private\n     */\n    _setActiveItem(index) {\n        this.activeItem?.setInactive(false);\n        this.activeItemIndex = index;\n        if (index >= 0) {\n            this.activeItem = this.items[index];\n            this._options.onItemActivated?.(this.activeItem.el);\n        } else {\n            this.activeItem = null;\n        }\n    }\n\n    /**\n     * @private\n     */\n    _updateActiveItemIndex(index) {\n        if (this.items[index]) {\n            this.items[index].setActive();\n        } else {\n            this.activeItemIndex = -1;\n            this.activeItem = null;\n        }\n    }\n\n    /**\n     * @private\n     */\n    _isNavigationAvailable(target) {\n        return this._options.isNavigationAvailable({ navigator: this, target });\n    }\n\n    /**\n     * @private\n     */\n    _checkFocus(target) {\n        if (!(target instanceof HTMLElement) || !this._isNavigationAvailable(target)) {\n            this._setActiveItem(-1);\n        }\n    }\n}\n\n/**\n * @typedef {Object} NavigationOptions\n * @property {() => HTMLElement[]} getItems\n * @property {({{ navigator: Navigator, target: HTMLElement }}) => bool} isNavigationAvailable\n * @property {NavigationHotkeys} hotkeys\n * @property {Function} onUpdated\n * @property {Function} onItemActivated\n * @property {Boolean} [virtualFocus=false] - If true, items are only visually\n * focused so the actual focus can be kept on another input.\n * @property {Boolean} [shouldFocusChildInput=false] - If true, elements like inputs or buttons\n * inside of the items are focused instead of the items themselves.\n * @property {Boolean} [shouldRegisterHotkeys=true] - If true, registers all hotkeys directly when\n * the hook is called.\n */\n\n/**\n * @typedef {{\n *  home: hotkeyHandler|HotkeyOptions|undefined,\n *  end: hotkeyHandler|HotkeyOptions|undefined,\n *  tab: hotkeyHandler|HotkeyOptions|undefined,\n *  \"shift+tab\": hotkeyHandler|HotkeyOptions|undefined,\n *  arrowup: hotkeyHandler|HotkeyOptions|undefined,\n *  arrowdown: hotkeyHandler|HotkeyOptions|undefined,\n *  enter: hotkeyHandler|HotkeyOptions|undefined,\n *  arrowleft: hotkeyHandler|HotkeyOptions|undefined,\n *  arrowright: hotkeyHandler|HotkeyOptions|undefined,\n *  escape: hotkeyHandler|HotkeyOptions|undefined,\n *  space: hotkeyHandler|HotkeyOptions|undefined,\n * }} NavigationHotkeys\n */\n\n/**\n * @typedef HotkeyOptions\n * @param {hotkeyHandler} callback\n * @param {({{ navigator: Navigator, target: HTMLElement }}) => bool} isAvailable\n * @param {boolean} bypassEditableProtection\n * @param {boolean} [allowRepeat=true]\n */\n\n/**\n * Callback used to override the behaviour of a specific\n * key input.\n *\n * @callback hotkeyHandler\n * @param {Navigator} navigator\n */\n\n/**\n * This hook adds keyboard navigation to items contained in an element.\n * It's purpose is to improve navigation in constrained context such\n * as dropdown and menus.\n *\n * This hook also has the following features:\n * - Hotkeys override and customization\n * - Navigation between inputs elements\n * - Optional virtual focus\n * - Focus on mouse enter\n *\n * @param {string|Object} containerRef\n * @param {NavigationOptions} options\n * @returns {Navigator}\n */\nexport function useNavigation(containerRef, options = {}) {\n    containerRef = typeof containerRef === \"string\" ? useRef(containerRef) : containerRef;\n\n    const newOptions = { ...options };\n    if (!newOptions.getItems) {\n        newOptions.getItems = () => containerRef.el?.querySelectorAll(\":scope .o-navigable\") ?? [];\n    }\n\n    const hotkeyService = useService(\"hotkey\");\n    const navigator = new Navigator(newOptions, hotkeyService);\n    const observer = new MutationObserver(() => navigator.update());\n\n    useEffect(\n        (containerEl) => {\n            if (containerEl) {\n                navigator.update();\n                observer.observe(containerEl, {\n                    childList: true,\n                    subtree: true,\n                });\n            }\n            return () => observer.disconnect();\n        },\n        () => [containerRef.el]\n    );\n\n    useExternalListener(browser, \"focus\", ({ target }) => navigator._checkFocus(target), true);\n    onWillUnmount(() => navigator._destroy());\n\n    return navigator;\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { makeErrorFromResponse, ConnectionLostError } from \"@web/core/network/rpc\";\nimport { browser } from \"@web/core/browser/browser\";\n\n/* eslint-disable */\n/**\n * The following sections are from libraries, they have been slightly modified\n * to allow patching them during tests, but should not be linted, so that we can\n * keep a minimal diff that is easy to reapply when upgrading\n */\n// -----------------------------------------------------------------------------\n// Content Disposition Library\n// -----------------------------------------------------------------------------\n\n/*\n(The MIT License)\nCopyright (c) 2014-2017 Douglas Christopher Wilson\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/**\n * Stripped down to only parsing/decoding.\n * Slightly changed for export and lint compliance\n */\n\n/**\n * RegExp to match percent encoding escape.\n * @private\n */\nconst HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g;\n\n/**\n * RegExp to match non-latin1 characters.\n * @private\n */\nconst NON_LATIN1_REGEXP = /[^\\x20-\\x7e\\xa0-\\xff]/g;\n\n/**\n * RegExp to match quoted-pair in RFC 2616\n *\n * quoted-pair = \"\\\" CHAR\n * CHAR        = <any US-ASCII character (octets 0 - 127)>\n * @private\n */\nconst QESC_REGEXP = /\\\\([\\u0000-\\u007f])/g;\n\n/**\n * RegExp for various RFC 2616 grammar\n *\n * parameter     = token \"=\" ( token | quoted-string )\n * token         = 1*<any CHAR except CTLs or separators>\n * separators    = \"(\" | \")\" | \"<\" | \">\" | \"@\"\n *               | \",\" | \";\" | \":\" | \"\\\" | <\">\n *               | \"/\" | \"[\" | \"]\" | \"?\" | \"=\"\n *               | \"{\" | \"}\" | SP | HT\n * quoted-string = ( <\"> *(qdtext | quoted-pair ) <\"> )\n * qdtext        = <any TEXT except <\">>\n * quoted-pair   = \"\\\" CHAR\n * CHAR          = <any US-ASCII character (octets 0 - 127)>\n * TEXT          = <any OCTET except CTLs, but including LWS>\n * LWS           = [CRLF] 1*( SP | HT )\n * CRLF          = CR LF\n * CR            = <US-ASCII CR, carriage return (13)>\n * LF            = <US-ASCII LF, linefeed (10)>\n * SP            = <US-ASCII SP, space (32)>\n * HT            = <US-ASCII HT, horizontal-tab (9)>\n * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>\n * OCTET         = <any 8-bit sequence of data>\n * @private\n */\nconst PARAM_REGEXP = /;[\\x09\\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*=[\\x09\\x20]*(\"(?:[\\x20!\\x23-\\x5b\\x5d-\\x7e\\x80-\\xff]|\\\\[\\x20-\\x7e])*\"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*/g;\n\n/**\n * RegExp for various RFC 5987 grammar\n *\n * ext-value     = charset  \"'\" [ language ] \"'\" value-chars\n * charset       = \"UTF-8\" / \"ISO-8859-1\" / mime-charset\n * mime-charset  = 1*mime-charsetc\n * mime-charsetc = ALPHA / DIGIT\n *               / \"!\" / \"#\" / \"$\" / \"%\" / \"&\"\n *               / \"+\" / \"-\" / \"^\" / \"_\" / \"`\"\n *               / \"{\" / \"}\" / \"~\"\n * language      = ( 2*3ALPHA [ extlang ] )\n *               / 4ALPHA\n *               / 5*8ALPHA\n * extlang       = *3( \"-\" 3ALPHA )\n * value-chars   = *( pct-encoded / attr-char )\n * pct-encoded   = \"%\" HEXDIG HEXDIG\n * attr-char     = ALPHA / DIGIT\n *               / \"!\" / \"#\" / \"$\" / \"&\" / \"+\" / \"-\" / \".\"\n *               / \"^\" / \"_\" / \"`\" / \"|\" / \"~\"\n * @private\n */\nconst EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/;\n\n/**\n * RegExp for various RFC 6266 grammar\n *\n * disposition-type = \"inline\" | \"attachment\" | disp-ext-type\n * disp-ext-type    = token\n * disposition-parm = filename-parm | disp-ext-parm\n * filename-parm    = \"filename\" \"=\" value\n *                  | \"filename*\" \"=\" ext-value\n * disp-ext-parm    = token \"=\" value\n *                  | ext-token \"=\" ext-value\n * ext-token        = <the characters in token, followed by \"*\">\n * @private\n */\nconst DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*(?:$|;)/;\n\n/**\n * Decode a RFC 6987 field value (gracefully).\n *\n * @param {string} str\n * @return {string}\n * @private\n */\nfunction decodefield(str) {\n    const match = EXT_VALUE_REGEXP.exec(str);\n\n    if (!match) {\n        throw new TypeError(\"invalid extended field value\");\n    }\n\n    const charset = match[1].toLowerCase();\n    const encoded = match[2];\n\n    switch (charset) {\n        case \"iso-8859-1\":\n            return encoded\n                .replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)\n                .replace(NON_LATIN1_REGEXP, \"?\");\n        case \"utf-8\":\n            return decodeURIComponent(encoded);\n        default:\n            throw new TypeError(\"unsupported charset in extended field\");\n    }\n}\n\n/**\n * Parse Content-Disposition header string.\n *\n * @param {string} string\n * @return {ContentDisposition}\n * @public\n */\nexport function parse(string) {\n    if (!string || typeof string !== \"string\") {\n        throw new TypeError(\"argument string is required\");\n    }\n\n    let match = DISPOSITION_TYPE_REGEXP.exec(string);\n\n    if (!match) {\n        throw new TypeError(\"invalid type format\");\n    }\n\n    // normalize type\n    let index = match[0].length;\n    const type = match[1].toLowerCase();\n\n    let key;\n    const names = [];\n    const params = {};\n    let value;\n\n    // calculate index to start at\n    index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === \";\" ? index - 1 : index;\n\n    // match parameters\n    while ((match = PARAM_REGEXP.exec(string))) {\n        if (match.index !== index) {\n            throw new TypeError(\"invalid parameter format\");\n        }\n\n        index += match[0].length;\n        key = match[1].toLowerCase();\n        value = match[2];\n\n        if (names.indexOf(key) !== -1) {\n            throw new TypeError(\"invalid duplicate parameter\");\n        }\n\n        names.push(key);\n\n        if (key.indexOf(\"*\") + 1 === key.length) {\n            // decode extended value\n            key = key.slice(0, -1);\n            value = decodefield(value);\n\n            // overwrite existing value\n            params[key] = value;\n            continue;\n        }\n\n        if (typeof params[key] === \"string\") {\n            continue;\n        }\n\n        if (value[0] === '\"') {\n            // remove quotes and escapes\n            value = value.substr(1, value.length - 2).replace(QESC_REGEXP, \"$1\");\n        }\n\n        params[key] = value;\n    }\n\n    if (index !== -1 && index !== string.length) {\n        throw new TypeError(\"invalid parameter format\");\n    }\n\n    return new ContentDisposition(type, params);\n}\n\n/**\n * Percent decode a single character.\n *\n * @param {string} str\n * @param {string} hex\n * @return {string}\n * @private\n */\nfunction pdecode(str, hex) {\n    return String.fromCharCode(parseInt(hex, 16));\n}\n\n/**\n * Class for parsed Content-Disposition header for v8 optimization\n *\n * @public\n * @param {string} type\n * @param {object} parameters\n * @constructor\n */\nfunction ContentDisposition(type, parameters) {\n    this.type = type;\n    this.parameters = parameters;\n}\n\n// -----------------------------------------------------------------------------\n// download.js library\n// -----------------------------------------------------------------------------\n\n/*\nMIT License\nCopyright (c) 2016 dandavis\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n */\n\n/**\n * download.js v4.2, by dandavis; 2008-2018. [MIT] see http://danml.com/download.html for tests/usage\n * v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime\n * v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs\n * v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.\n * v4 adds AMD/UMD, commonJS, and plain browser support\n * v4.1 adds url download capability via solo URL argument (same domain/CORS only)\n * v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors\n *\n * Slightly modified for export and lint compliance\n *\n * @param {Blob | File | String} data\n * @param {String} [filename]\n * @param {String} [mimetype]\n */\nfunction _download(data, filename, mimetype) {\n    let self = window, // this script is only for browsers anyway...\n        defaultMime = \"application/octet-stream\", // this default mime also triggers iframe downloads\n        mimeType = mimetype || defaultMime,\n        payload = data,\n        url = !filename && !mimetype && payload,\n        anchor = document.createElement(\"a\"),\n        toString = function (a) {\n            return String(a);\n        },\n        myBlob = self.Blob || self.MozBlob || self.WebKitBlob || toString,\n        fileName = filename || \"download\",\n        blob,\n        reader;\n    myBlob = myBlob.call ? myBlob.bind(self) : Blob;\n\n    if (String(this) === \"true\") {\n        //reverse arguments, allowing download.bind(true, \"text/xml\", \"export.xml\") to act as a callback\n        payload = [payload, mimeType];\n        mimeType = payload[0];\n        payload = payload[1];\n    }\n\n    if (url && url.length < 2048) {\n        // if no filename and no mime, assume a url was passed as the only argument\n        fileName = url.split(\"/\").pop().split(\"?\")[0];\n        anchor.href = url; // assign href prop to temp anchor\n        if (anchor.href.indexOf(url) !== -1) {\n            // if the browser determines that it's a potentially valid url path:\n            return new Promise((resolve, reject) => {\n                let xhr = new browser.XMLHttpRequest();\n                xhr.open(\"GET\", url, true);\n                configureBlobDownloadXHR(xhr, {\n                    onSuccess: resolve,\n                    onFailure: reject,\n                    url\n                });\n                xhr.send();\n            });\n        }\n    }\n\n    //go ahead and download dataURLs right away\n    if (/^data:[\\w+\\-]+\\/[\\w+\\-]+[,;]/.test(payload)) {\n        if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {\n            payload = dataUrlToBlob(payload);\n            mimeType = payload.type || defaultMime;\n        } else {\n            return navigator.msSaveBlob // IE10 can't do a[download], only Blobs:\n                ? navigator.msSaveBlob(dataUrlToBlob(payload), fileName)\n                : saver(payload); // everyone else can save dataURLs un-processed\n        }\n    }\n\n    blob = payload instanceof myBlob ? payload : new myBlob([payload], { type: mimeType });\n\n    function dataUrlToBlob(strUrl) {\n        let parts = strUrl.split(/[:;,]/),\n            type = parts[1],\n            decoder = parts[2] === \"base64\" ? atob : decodeURIComponent,\n            binData = decoder(parts.pop()),\n            mx = binData.length,\n            i = 0,\n            uiArr = new Uint8Array(mx);\n\n        for (i; i < mx; ++i) {\n            uiArr[i] = binData.charCodeAt(i);\n        }\n\n        return new myBlob([uiArr], { type });\n    }\n\n    function saver(url, winMode) {\n        if (\"download\" in anchor) {\n            //html5 A[download]\n            anchor.href = url;\n            anchor.setAttribute(\"download\", fileName);\n            anchor.className = \"download-js-link\";\n            anchor.innerText = _t(\"downloading...\");\n            anchor.style.display = \"none\";\n            anchor.target = \"_blank\";\n            document.body.appendChild(anchor);\n            setTimeout(() => {\n                anchor.click();\n                document.body.removeChild(anchor);\n                if (winMode === true) {\n                    setTimeout(() => {\n                        self.URL.revokeObjectURL(anchor.href);\n                    }, 250);\n                }\n            }, 66);\n            return true;\n        }\n\n        // handle non-a[download] safari as best we can:\n        if (/(Version)\\/(\\d+)\\.(\\d+)(?:\\.(\\d+))?.*Safari\\//.test(navigator.userAgent)) {\n            url = url.replace(/^data:([\\w\\/\\-+]+)/, defaultMime);\n            if (!window.open(url)) {\n                // popup blocked, offer direct download:\n                if (\n                    confirm(\n                        \"Displaying New Document\\n\\nUse Save As... to download, then click back to return to this page.\"\n                    )\n                ) {\n                    location.href = url;\n                }\n            }\n            return true;\n        }\n\n        //do iframe dataURL download (old ch+FF):\n        let f = document.createElement(\"iframe\");\n        document.body.appendChild(f);\n\n        if (!winMode) {\n            // force a mime that will download:\n            url = `data:${url.replace(/^data:([\\w\\/\\-+]+)/, defaultMime)}`;\n        }\n        f.src = url;\n        setTimeout(() => {\n            document.body.removeChild(f);\n        }, 333);\n    }\n\n    if (navigator.msSaveBlob) {\n        // IE10+ : (has Blob, but not a[download] or URL)\n        return navigator.msSaveBlob(blob, fileName);\n    }\n\n    if (self.URL) {\n        // simple fast and modern way using Blob and URL:\n        saver(self.URL.createObjectURL(blob), true);\n    } else {\n        // handle non-Blob()+non-URL browsers:\n        if (typeof blob === \"string\" || blob.constructor === toString) {\n            try {\n                return saver(`data:${mimeType};base64,${self.btoa(blob)}`);\n            } catch {\n                return saver(`data:${mimeType},${encodeURIComponent(blob)}`);\n            }\n        }\n\n        // Blob but not URL support:\n        reader = new FileReader();\n        reader.onload = function () {\n            saver(this.result);\n        };\n        reader.readAsDataURL(blob);\n    }\n    return true;\n}\n/* eslint-enable */\n\n// -----------------------------------------------------------------------------\n// Exported download functions\n// -----------------------------------------------------------------------------\n\n/**\n * Download data as a file\n *\n * @param {Object} data\n * @param {String} filename\n * @param {String} mimetype\n * @returns {Boolean}\n *\n * Note: the actual implementation is certainly unconventional, but sadly\n * necessary to be able to test code using the download function\n */\nexport function downloadFile(data, filename, mimetype) {\n    return downloadFile._download(data, filename, mimetype);\n}\ndownloadFile._download = _download;\n\n/**\n * Download a file from form or server url\n *\n * This function is meant to call a controller with some data\n * and download the response.\n *\n * Note: the actual implementation is certainly unconventional, but sadly\n * necessary to be able to test code using the download function\n *\n * @param {*} options\n * @returns {Promise<any>}\n */\nexport function download(options) {\n    return download._download(options);\n}\n\ndownload._download = (options) => {\n    return new Promise((resolve, reject) => {\n        const xhr = new browser.XMLHttpRequest();\n        let data;\n        if (Object.prototype.hasOwnProperty.call(options, \"form\")) {\n            xhr.open(options.form.method, options.form.action);\n            data = new FormData(options.form);\n        } else {\n            xhr.open(\"POST\", options.url);\n            data = new FormData();\n            Object.entries(options.data).forEach((entry) => {\n                const [key, value] = entry;\n                data.append(key, value);\n            });\n        }\n        data.append(\"token\", \"dummy-because-api-expects-one\");\n        if (odoo.csrf_token) {\n            data.append(\"csrf_token\", odoo.csrf_token);\n        }\n        configureBlobDownloadXHR(xhr, {\n            onSuccess: resolve,\n            onFailure: reject,\n            url: options.url,\n        });\n        xhr.send(data);\n    });\n};\n\n/**\n * Setup a download xhr request response handling\n * (onload, onerror, responseType), with hooks when the download succeeds or\n * fails.\n *\n * @param {XMLHttpRequest} xhr\n * @param {object} [options]\n * @param {(filename: string) => void} [options.onSuccess]\n * @param {(Error) => void} [options.onFailure]\n * @param {string} [options.url]\n */\nexport function configureBlobDownloadXHR(\n    xhr,\n    { onSuccess = () => {}, onFailure = () => {}, url } = {}\n) {\n    xhr.responseType = \"blob\";\n    xhr.onload = () => {\n        const mimetype = xhr.response.type;\n        const header = (xhr.getResponseHeader(\"Content-Disposition\") || \"\").replace(/;$/, \"\");\n        // replace because apparently we send some C-D headers with a trailing \";\"\n        const filename = header ? parse(header).parameters.filename : null;\n        // In Odoo, the default mimetype, including for JSON errors is text/html (ref: http.py:Root.get_response )\n        // in that case, in order to also be able to download html files, we check if we get a proper filename to be able to download\n        if (xhr.status === 200 && (mimetype !== \"text/html\" || filename)) {\n            _download(xhr.response, filename, mimetype);\n            onSuccess(filename);\n        } else if (xhr.status === 502) {\n            // If Odoo is behind another server (nginx)\n            onFailure(new ConnectionLostError(url));\n        } else {\n            const decoder = new FileReader();\n            decoder.onload = () => {\n                const contents = decoder.result;\n                const doc = new DOMParser().parseFromString(contents, \"text/html\");\n                const nodes =\n                    doc.body.children.length === 0 ? [doc.body] : doc.body.children;\n\n                let error;\n                try {\n                    // a Serialized python Error\n                    const node = nodes[1] || nodes[0];\n                    error = JSON.parse(node.textContent);\n                } catch {\n                    error = {\n                        message: \"Arbitrary Uncaught Python Exception\",\n                        data: {\n                            debug:\n                                `${xhr.status}` +\n                                `\\n` +\n                                `${nodes.length > 0 ? nodes[0].textContent : \"\"}\n                                ${nodes.length > 1 ? nodes[1].textContent : \"\"}`,\n                        },\n                    };\n                }\n                error = makeErrorFromResponse(error);\n                onFailure(error);\n            };\n            decoder.readAsText(xhr.response);\n        }\n    };\n    xhr.onerror = () => {\n        onFailure(new ConnectionLostError(url));\n    };\n}\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"../registry\";\n\nfunction checkResponseStatus(response) {\n    if (response.status === 502) {\n        throw new Error(\"Failed to fetch\");\n    }\n    if (response.status === 413) {\n        throw new Error(\"Content too large\");\n    }\n}\n\nexport async function get(route, readMethod = \"json\") {\n    const response = await browser.fetch(route, { method: \"GET\" });\n    checkResponseStatus(response);\n    return response[readMethod]();\n}\n\nexport async function post(route, params = {}, readMethod = \"json\") {\n    let formData = params;\n    if (!(formData instanceof FormData)) {\n        formData = new FormData();\n        for (const key in params) {\n            const value = params[key];\n            if (Array.isArray(value) && value.length) {\n                for (const val of value) {\n                    formData.append(key, val);\n                }\n            } else {\n                formData.append(key, value);\n            }\n        }\n    }\n    const response = await browser.fetch(route, {\n        body: formData,\n        method: \"POST\",\n    });\n    checkResponseStatus(response);\n    return response[readMethod]();\n}\n\nexport const httpService = {\n    start() {\n        return { get, post };\n    },\n};\n\nregistry.category(\"services\").add(\"http\", httpService);\n", "import { EventBus } from \"@odoo/owl\";\nimport { browser } from \"../browser/browser\";\nimport { omit } from \"../utils/objects\";\n\n/**\n * @typedef {{\n *  code: number;\n *  message: string;\n *  data?: unknown;\n *  type?: string;\n * }} JsonRpcError\n */\n\nexport const rpcBus = new EventBus();\n\nconst RPC_SETTINGS = new Set([\"cache\", \"silent\", \"xhr\", \"headers\"]);\nfunction validateRPCSettings(settings) {\n    if (!Object.keys(settings).every((key) => RPC_SETTINGS.has(key))) {\n        throw new Error(`The settings for rpc should be ${[...RPC_SETTINGS].join(\" \")}`);\n    }\n    if (\"cache\" in settings && \"xhr\" in settings) {\n        throw new Error(\"Can't use 'cache' and 'xhr' at the same time\");\n    }\n}\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\nexport class RPCError extends Error {\n    constructor() {\n        super(...arguments);\n        this.name = \"RPC_ERROR\";\n        this.type = \"server\";\n        this.code = null;\n        this.data = null;\n        this.exceptionName = null;\n        this.subType = null;\n    }\n}\n\nexport class ConnectionLostError extends Error {\n    constructor(url, ...args) {\n        const message = url\n            ? `Connection to \"${url}\" couldn't be established or was interrupted`\n            : \"Connection couldn't be established or was interrupted\";\n        super(message, ...args);\n        this.url = url;\n    }\n}\n\nexport class ConnectionAbortedError extends Error {}\n\n/**\n * @param {JsonRpcError} response\n */\nexport function makeErrorFromResponse(response) {\n    // Odoo returns error like this, in a error field instead of properly\n    // using http error codes...\n    const { code, data: errorData, message, type: subType } = response;\n    const error = new RPCError();\n    error.exceptionName = errorData?.name;\n    error.subType = subType;\n    error.data = errorData;\n    error.message = message;\n    error.code = code;\n    return error;\n}\n\n// -----------------------------------------------------------------------------\n// Cache RPC method\n// -----------------------------------------------------------------------------\n\nlet rpcCache;\n\nrpc.setCache = function (cache) {\n    rpcCache = cache;\n};\n\nrpcBus.addEventListener(\"CLEAR-CACHES\", (event) => {\n    rpcCache?.invalidate(event.detail);\n});\n\n// -----------------------------------------------------------------------------\n// Main RPC\n// -----------------------------------------------------------------------------\nlet rpcId = 0;\nexport function rpc(url, params = {}, settings = {}) {\n    return rpc._rpc(url, params, settings);\n}\n// such that it can be overriden in tests\nrpc._rpc = function (url, params, settings) {\n    validateRPCSettings(settings);\n    if (settings.cache && rpcCache) {\n        return rpcCache.read(\n            params?.method || url, // table\n            JSON.stringify({ url, params }), // key\n            () => rpc._rpc(url, params, omit(settings, \"cache\")),\n            typeof settings.cache === \"boolean\" ? {} : settings.cache // cache can be boolean or an object with options (or an empty object of course)\n        );\n    }\n    const XHR = browser.XMLHttpRequest;\n    const data = {\n        id: rpcId++,\n        jsonrpc: \"2.0\",\n        method: \"call\",\n        params: params,\n    };\n    const request = settings.xhr || new XHR();\n    let rejectFn;\n    const promise = new Promise((resolve, reject) => {\n        rejectFn = reject;\n        rpcBus.trigger(\"RPC:REQUEST\", { data, url, settings });\n        // handle success\n        request.addEventListener(\"load\", () => {\n            if (request.status === 502) {\n                // If Odoo is behind another server (eg.: nginx)\n                const error = new ConnectionLostError(url);\n                rpcBus.trigger(\"RPC:RESPONSE\", { data, settings, error });\n                reject(error);\n                return;\n            }\n            let params;\n            try {\n                params = JSON.parse(request.response);\n            } catch {\n                // the response isn't json parsable, which probably means that the rpc request could\n                // not be handled by the server, e.g. PoolError('The Connection Pool Is Full')\n                const error = new ConnectionLostError(url);\n                rpcBus.trigger(\"RPC:RESPONSE\", { data, settings, error });\n                return reject(error);\n            }\n            const { error: responseError, result: responseResult } = params;\n            if (!params.error) {\n                rpcBus.trigger(\"RPC:RESPONSE\", { data, settings, result: params.result });\n                return resolve(responseResult);\n            }\n            const error = makeErrorFromResponse(responseError);\n            error.model = data.params.model;\n            rpcBus.trigger(\"RPC:RESPONSE\", { data, settings, error });\n            reject(error);\n        });\n        // handle failure\n        request.addEventListener(\"error\", () => {\n            const error = new ConnectionLostError(url);\n            rpcBus.trigger(\"RPC:RESPONSE\", { data, settings, error });\n            reject(error);\n        });\n        // configure and send request\n        request.open(\"POST\", url);\n        const headers = settings.headers || {};\n        headers[\"Content-Type\"] = \"application/json\";\n        for (const [header, value] of Object.entries(headers)) {\n            request.setRequestHeader(header, value);\n        }\n        request.send(JSON.stringify(data));\n    });\n    /**\n     * @param {Boolean} rejectError Returns an error if true. Allows you to cancel\n     *                  ignored rpc's in order to unblock the ui and not display an error.\n     */\n    promise.abort = function (rejectError = true) {\n        if (request.abort) {\n            request.abort();\n        }\n        const error = new ConnectionAbortedError(\"XmlHttpRequestError abort\");\n        rpcBus.trigger(\"RPC:RESPONSE\", { data, settings, error });\n        if (rejectError) {\n            rejectFn(error);\n        }\n    };\n    return promise;\n};\n", "import { Deferred } from \"@web/core/utils/concurrency\";\nimport { IDBQuotaExceededError, IndexedDB } from \"@web/core/utils/indexed_db\";\nimport { deepCopy } from \"../utils/objects\";\n\n/**\n * @typedef {{\n * callback?: function;\n * type?: \"ram\" | \"disk\";\n * update?: \"once\" | \"always\";\n * }} RPCCacheSettings\n */\n\nfunction jsonEqual(v1, v2) {\n    return JSON.stringify(v1) === JSON.stringify(v2);\n}\n\nfunction validateSettings({ type, update }) {\n    if (![\"ram\", \"disk\"].includes(type)) {\n        throw new Error(`Invalid \"type\" settings provided to RPCCache: ${type}`);\n    }\n    if (![\"always\", \"once\"].includes(update)) {\n        throw new Error(`Invalid \"update\" settings provided to RPCCache: ${update}`);\n    }\n}\n\nconst CRYPTO_ALGO = \"AES-GCM\";\nconst MAX_STORAGE_SIZE = 2 * 1024 * 1024 * 1024; // 2Gb\n\nclass Crypto {\n    constructor(secret) {\n        this._cryptoKey = null;\n        this._ready = window.crypto.subtle\n            .importKey(\n                \"raw\",\n                new Uint8Array(secret.match(/../g).map((h) => parseInt(h, 16))).buffer,\n                CRYPTO_ALGO,\n                false,\n                [\"encrypt\", \"decrypt\"]\n            )\n            .then((encryptedKey) => {\n                this._cryptoKey = encryptedKey;\n            });\n    }\n\n    async encrypt(value) {\n        await this._ready;\n        // The iv must never be reused with a given key.\n        const iv = window.crypto.getRandomValues(new Uint8Array(12));\n        const ciphertext = await window.crypto.subtle.encrypt(\n            {\n                name: CRYPTO_ALGO,\n                iv,\n                length: 64, // length of the counter in bits\n            },\n            this._cryptoKey,\n            new TextEncoder().encode(JSON.stringify(value)) // encoded Data\n        );\n        return { ciphertext, iv };\n    }\n\n    async decrypt({ ciphertext, iv }) {\n        await this._ready;\n        const decrypted = await window.crypto.subtle.decrypt(\n            {\n                name: CRYPTO_ALGO,\n                iv,\n                length: 64,\n            },\n            this._cryptoKey,\n            ciphertext\n        );\n        return JSON.parse(new TextDecoder().decode(decrypted));\n    }\n}\n\nclass RamCache {\n    constructor() {\n        this.ram = {};\n    }\n\n    write(table, key, value) {\n        if (!(table in this.ram)) {\n            this.ram[table] = {};\n        }\n        this.ram[table][key] = value;\n    }\n\n    read(table, key) {\n        return this.ram[table]?.[key];\n    }\n\n    delete(table, key) {\n        delete this.ram[table]?.[key];\n    }\n\n    invalidate(tables = null) {\n        if (tables) {\n            tables = typeof tables === \"string\" ? [tables] : tables;\n            for (const table of tables) {\n                if (table in this.ram) {\n                    this.ram[table] = {};\n                }\n            }\n        } else {\n            this.ram = {};\n        }\n    }\n}\n\nexport class RPCCache {\n    constructor(name, version, secret) {\n        this.crypto = new Crypto(secret);\n        this.indexedDB = new IndexedDB(name, version + CRYPTO_ALGO);\n        this.ramCache = new RamCache();\n        this.pendingRequests = {};\n        this.checkSize(); // we want to control the disk space used by Odoo\n    }\n\n    async checkSize() {\n        const { usage } = await navigator.storage.estimate();\n        if (usage > MAX_STORAGE_SIZE) {\n            console.log(`Deleting indexedDB database as maximum storage size is reached`);\n            return this.indexedDB.deleteDatabase();\n        }\n    }\n\n    /**\n     * @param {string} table\n     * @param {string} key\n     * @param {function} fallback\n     * @param {RPCCacheSettings} settings\n     */\n    read(table, key, fallback, { callback = () => {}, type = \"ram\", update = \"once\" } = {}) {\n        validateSettings({ type, update });\n\n        let ramValue = this.ramCache.read(table, key);\n\n        const requestKey = `${table}/${key}`;\n        const hasPendingRequest = requestKey in this.pendingRequests;\n        if (hasPendingRequest) {\n            // never do the same call multiple times in parallel => return the same value for all\n            // those calls, but store their callback to call them when/if the real value is obtained\n            this.pendingRequests[requestKey].callbacks.push(callback);\n            return ramValue.then((result) => deepCopy(result));\n        }\n\n        if (!ramValue || update === \"always\") {\n            const request = { callbacks: [callback], invalidated: false };\n            this.pendingRequests[requestKey] = request;\n\n            // execute the fallback and write the result in the caches\n            const prom = new Promise((resolve, reject) => {\n                const fromCache = new Deferred();\n                let fromCacheValue;\n                const onFullfilled = (result) => {\n                    resolve(result);\n                    // call the pending request callbacks with the result\n                    const hasChanged = !!fromCacheValue && !jsonEqual(fromCacheValue, result);\n                    request.callbacks.forEach((cb) => cb(deepCopy(result), hasChanged));\n                    if (request.invalidated) {\n                        return result;\n                    }\n                    delete this.pendingRequests[requestKey];\n                    // update the ram and optionally the disk caches with the latest data\n                    this.ramCache.write(table, key, Promise.resolve(result));\n                    if (type === \"disk\") {\n                        this.crypto.encrypt(result).then((encryptedResult) => {\n                            this.indexedDB.write(table, key, encryptedResult).catch((e) => {\n                                if (e instanceof IDBQuotaExceededError) {\n                                    this.indexedDB.deleteDatabase();\n                                } else {\n                                    throw e;\n                                }\n                            });\n                        });\n                    }\n                    return result;\n                };\n                const onRejected = async (error) => {\n                    await fromCache;\n                    if (!request.invalidated) {\n                        delete this.pendingRequests[requestKey];\n                        if (!fromCacheValue) {\n                            this.ramCache.delete(table, key); // remove rejected prom from ram cache\n                        }\n                    }\n                    if (fromCacheValue) {\n                        // promise has already been fullfilled with the cached value\n                        throw error;\n                    }\n                    reject(error);\n                };\n                fallback().then(onFullfilled, onRejected);\n\n                // speed up the request by using the caches\n                if (ramValue) {\n                    // ramValue is always already resolved here, as it can't be pending (otherwise\n                    // we would have early returned because of `pendingRequests`) and it would have\n                    // been removed from the ram cache if it had been rejected\n                    // => no need to define a `catch` callback.\n                    ramValue.then((value) => {\n                        resolve(value);\n                        fromCacheValue = value;\n                        fromCache.resolve();\n                    });\n                } else if (type === \"disk\") {\n                    this.indexedDB\n                        .read(table, key)\n                        .then(async (result) => {\n                            if (result) {\n                                let decrypted;\n                                try {\n                                    decrypted = await this.crypto.decrypt(result);\n                                } catch {\n                                    // Do nothing ! The cryptoKey is probably different.\n                                    // The data will be updated with the new cryptoKey.\n                                    return;\n                                }\n                                resolve(decrypted);\n                                fromCacheValue = decrypted;\n                            }\n                        })\n                        .finally(() => fromCache.resolve());\n                } else {\n                    fromCache.resolve(); // fromCacheValue will remain undefined\n                }\n            });\n            this.ramCache.write(table, key, prom);\n            ramValue = prom;\n        }\n\n        return ramValue.then((result) => deepCopy(result));\n    }\n\n    invalidate(tables) {\n        this.indexedDB.invalidate(tables);\n        this.ramCache.invalidate(tables);\n        // flag the pending requests as invalidated s.t. we don't write their results in caches\n        for (const key in this.pendingRequests) {\n            this.pendingRequests[key].invalidated = true;\n        }\n        this.pendingRequests = {};\n    }\n}\n", "import { Component, onWillRender, onWillUpdateProps, useEffect, useRef, useState } from \"@odoo/owl\";\n\n/**\n * A notebook component that will render only the current page and allow\n * switching between its pages.\n *\n * You can also set pages using a template component. Use an array with\n * the `pages` props to do such rendering.\n *\n * Pages can also specify their index in the notebook.\n *\n *      e.g.:\n *          PageTemplate.template = xml`\n                    <h1 t-esc=\"props.heading\" />\n                    <p t-esc=\"props.text\" />`;\n\n *      `pages` could be:\n *      [\n *          {\n *              Component: PageTemplate,\n *              id: 'unique_id' // optional: can be given as defaultPage props to the notebook\n *              index: 1 // optional: page position in the notebook\n *              name: 'some_name' // optional\n *              title: \"Some Title 1\", // title displayed on the tab pane\n *              props: {\n *                  heading: \"Page 1\",\n *                  text: \"Text Content 1\",\n *              },\n *          },\n *          {\n *              Component: PageTemplate,\n *              title: \"Some Title 2\",\n *              props: {\n *                  heading: \"Page 2\",\n *                  text: \"Text Content 2\",\n *              },\n *          },\n *      ]\n *\n * <Notebook pages=\"pages\">\n *    <t t-set-slot=\"Page Name 1\" title=\"Some Title\" isVisible=\"bool\">\n *      <div>Page Content 1</div>\n *    </t>\n *    <t t-set-slot=\"Page Name 2\" title=\"Some Title\" isVisible=\"bool\">\n *      <div>Page Content 2</div>\n *    </t>\n * </Notebook>\n *\n * @extends Component\n */\n\nexport class Notebook extends Component {\n    static template = \"web.Notebook\";\n    static defaultProps = {\n        className: \"\",\n        orientation: \"horizontal\",\n        onPageUpdate: () => {},\n    };\n    static props = {\n        slots: { type: Object, optional: true },\n        pages: { type: Object, optional: true },\n        class: { optional: true },\n        className: { type: String, optional: true },\n        defaultPage: { type: String, optional: true },\n        orientation: { type: String, optional: true },\n        icons: { type: Object, optional: true },\n        onPageUpdate: { type: Function, optional: true },\n    };\n\n    setup() {\n        this.activePane = useRef(\"activePane\");\n        this.pages = this.computePages(this.props);\n        this.invalidPages = new Set();\n        this.state = useState({ currentPage: null });\n        this.state.currentPage = this.computeActivePage(this.props.defaultPage, true);\n        useEffect(\n            () => {\n                this.props.onPageUpdate(this.state.currentPage);\n                this.activePane.el?.classList.add(\"show\");\n            },\n            () => [this.state.currentPage]\n        );\n        onWillRender(() => {\n            this.computeInvalidPages();\n        });\n        onWillUpdateProps((nextProps) => {\n            const activateDefault =\n                this.props.defaultPage !== nextProps.defaultPage || !this.defaultVisible;\n            this.pages = this.computePages(nextProps);\n            this.state.currentPage = this.computeActivePage(nextProps.defaultPage, activateDefault);\n        });\n    }\n\n    get navItems() {\n        return this.pages.filter((e) => e[1].isVisible);\n    }\n\n    get page() {\n        const page = this.pages.find((e) => e[0] === this.state.currentPage)[1];\n        return page.Component && page;\n    }\n\n    activatePage(pageIndex) {\n        if (!this.disabledPages.includes(pageIndex) && this.state.currentPage !== pageIndex) {\n            this.activePane.el?.classList.remove(\"show\");\n            this.state.currentPage = pageIndex;\n        }\n    }\n\n    computePages(props) {\n        if (!props.slots && !props.pages) {\n            return [];\n        }\n        if (props.pages) {\n            for (const page of props.pages) {\n                page.isVisible = true;\n            }\n        }\n        this.disabledPages = [];\n        const pages = [];\n        const pagesWithIndex = [];\n        for (const [k, v] of Object.entries({ ...props.slots, ...props.pages })) {\n            const id = v.id || k;\n            if (v.index) {\n                pagesWithIndex.push([id, v]);\n            } else {\n                pages.push([id, v]);\n            }\n            if (v.isDisabled) {\n                this.disabledPages.push(k);\n            }\n        }\n        for (const page of pagesWithIndex) {\n            pages.splice(page[1].index, 0, page);\n        }\n        return pages;\n    }\n\n    computeActivePage(defaultPage, activateDefault) {\n        if (!this.pages.length) {\n            return null;\n        }\n        const pages = this.pages.filter((e) => e[1].isVisible).map((e) => e[0]);\n\n        if (defaultPage) {\n            if (!pages.includes(defaultPage)) {\n                this.defaultVisible = false;\n            } else {\n                this.defaultVisible = true;\n                if (activateDefault) {\n                    return defaultPage;\n                }\n            }\n        }\n        const current = this.state.currentPage;\n        if (!current || (current && !pages.includes(current))) {\n            return pages[0];\n        }\n\n        return current;\n    }\n\n    computeInvalidPages() {\n        this.invalidPages = new Set();\n        for (const page of this.navItems) {\n            const invalid = page[1].fieldNames?.some((fieldName) =>\n                this.env.model?.root.isFieldInvalid(fieldName)\n            );\n            if (invalid) {\n                this.invalidPages.add(page[0]);\n            }\n        }\n    }\n}\n", "import { Component, useRef, onMounted } from \"@odoo/owl\";\n\nconst AUTOCLOSE_DELAY = 4000;\n\nexport class Notification extends Component {\n    static template = \"web.NotificationWowl\";\n    static props = {\n        message: {\n            validate: (m) =>\n                typeof m === \"string\" ||\n                (typeof m === \"object\" && typeof m.toString === \"function\"),\n        },\n        type: {\n            type: String,\n            optional: true,\n            validate: (t) => [\"warning\", \"danger\", \"success\", \"info\"].includes(t),\n        },\n        title: { type: [String, Boolean, { toString: Function }], optional: true },\n        className: { type: String, optional: true },\n        buttons: {\n            type: Array,\n            element: {\n                type: Object,\n                shape: {\n                    name: { type: String },\n                    icon: { type: String, optional: true },\n                    primary: { type: Boolean, optional: true },\n                    onClick: Function,\n                },\n            },\n            optional: true,\n        },\n        sticky: { type: Boolean, optional: true },\n        autocloseDelay: { type: Number, optional: true },\n        close: { type: Function },\n    };\n    static defaultProps = {\n        buttons: [],\n        className: \"\",\n        type: \"warning\",\n        autocloseDelay: AUTOCLOSE_DELAY,\n    };\n    setup() {\n        this.autocloseProgress = useRef(\"autoclose_progress_bar\");\n        onMounted(() => this.startNotificationTimer());\n    }\n\n    freeze() {\n        this.startedTimestamp = false;\n        this.autocloseProgress.el.style.width = 0;\n    }\n\n    refresh() {\n        this.startNotificationTimer();\n    }\n\n    close() {\n        this.props.close();\n    }\n\n    startNotificationTimer() {\n        if (this.props.sticky) {\n            return;\n        }\n        this.startedTimestamp = luxon.DateTime.now().ts;\n\n        const cb = () => {\n            if (this.startedTimestamp) {\n                const currentProgress =\n                    (luxon.DateTime.now().ts - this.startedTimestamp) / this.props.autocloseDelay;\n                if (currentProgress > 1) {\n                    this.close();\n                    return;\n                }\n                if (this.autocloseProgress.el) {\n                    this.autocloseProgress.el.style.width = `${(1 - currentProgress) * 100}%`;\n                }\n                requestAnimationFrame(cb);\n            }\n        };\n        cb();\n    }\n}\n", "import { Notification } from \"./notification\";\nimport { Transition } from \"@web/core/transition\";\n\nimport { Component, xml, useState } from \"@odoo/owl\";\n\nexport class NotificationContainer extends Component {\n    static props = {\n        notifications: Object,\n    };\n\n    static template = xml`\n        <div class=\"o_notification_manager\">\n            <t t-foreach=\"notifications\" t-as=\"notification\" t-key=\"notification\">\n                <Transition leaveDuration=\"0\" immediate=\"true\" name=\"'o_notification_fade'\" t-slot-scope=\"transition\">\n                    <Notification t-props=\"notification_value.props\" className=\"(notification_value.props.className || '') + ' ' + transition.className\"/>\n                </Transition>\n            </t>\n        </div>`;\n    static components = { Notification, Transition };\n\n    setup() {\n        this.notifications = useState(this.props.notifications);\n    }\n}\n", "import { registry } from \"../registry\";\nimport { NotificationContainer } from \"./notification_container\";\n\nimport { reactive } from \"@odoo/owl\";\n\n/**\n * @typedef {Object} NotificationButton\n * @property {string} name\n * @property {string} [icon]\n * @property {boolean} [primary=false]\n * @property {function(): void} onClick\n *\n * @typedef {Object} NotificationOptions\n * @property {string} [title]\n * @property {number} [autocloseDelay=4000]\n * @property {\"warning\" | \"danger\" | \"success\" | \"info\"} [type]\n * @property {boolean} [sticky=false]\n * @property {string} [className]\n * @property {function(): void} [onClose]\n * @property {NotificationButton[]} [buttons]\n */\n\nexport const notificationService = {\n    notificationContainer: NotificationContainer,\n\n    start() {\n        let notifId = 0;\n        const notifications = reactive({});\n\n        registry.category(\"main_components\").add(\n            this.notificationContainer.name,\n            {\n                Component: this.notificationContainer,\n                props: { notifications },\n            },\n            { sequence: 100 }\n        );\n\n        /**\n         * @param {string} message\n         * @param {NotificationOptions} [options]\n         */\n        function add(message, options = {}) {\n            const id = ++notifId;\n            const closeFn = () => close(id);\n            const props = Object.assign({}, options, { message, close: closeFn });\n            delete props.onClose;\n            const notification = {\n                id,\n                props,\n                onClose: options.onClose,\n            };\n            notifications[id] = notification;\n            return closeFn;\n        }\n\n        function close(id) {\n            if (notifications[id]) {\n                const notification = notifications[id];\n                if (notification.onClose) {\n                    notification.onClose();\n                }\n                delete notifications[id];\n            }\n        }\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"notification\", notificationService);\n", "import { registry } from \"@web/core/registry\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { user } from \"@web/core/user\";\nimport { Domain } from \"@web/core/domain\";\n\n/**\n * This ORM service is the standard way to interact with the ORM in python from\n * the javascript codebase.\n */\n\n// -----------------------------------------------------------------------------\n// ORM\n// -----------------------------------------------------------------------------\n\n/**\n * One2many and Many2many fields expect a special command to manipulate the\n * relation they implement.\n *\n * Internally, each command is a 3-elements tuple where the first element is a\n * mandatory integer that identifies the command, the second element is either\n * the related record id to apply the command on (commands update, delete,\n * unlink and link) either 0 (commands create, clear and set), the third\n * element is either the ``values`` to write on the record (commands create\n * and update) either the new ``ids`` list of related records (command set),\n * either 0 (commands delete, unlink, link, and clear).\n */\nexport const x2ManyCommands = {\n    // (0, virtualID | false, { values })\n    CREATE: 0,\n    create(virtualID, values) {\n        delete values.id;\n        return [x2ManyCommands.CREATE, virtualID || false, values];\n    },\n    // (1, id, { values })\n    UPDATE: 1,\n    update(id, values) {\n        delete values.id;\n        return [x2ManyCommands.UPDATE, id, values];\n    },\n    // (2, id[, _])\n    DELETE: 2,\n    delete(id) {\n        return [x2ManyCommands.DELETE, id, false];\n    },\n    // (3, id[, _]) removes relation, but not linked record itself\n    UNLINK: 3,\n    unlink(id) {\n        return [x2ManyCommands.UNLINK, id, false];\n    },\n    // (4, id[, _])\n    LINK: 4,\n    link(id) {\n        return [x2ManyCommands.LINK, id, false];\n    },\n    // (5[, _[, _]])\n    CLEAR: 5,\n    clear() {\n        return [x2ManyCommands.CLEAR, false, false];\n    },\n    // (6, _, ids) replaces all linked records with provided ids\n    SET: 6,\n    set(ids) {\n        return [x2ManyCommands.SET, false, ids];\n    },\n};\n\nfunction validateModel(value) {\n    if (typeof value !== \"string\" || value.length === 0) {\n        throw new Error(`Invalid model name: ${value}`);\n    }\n}\nfunction validatePrimitiveList(name, type, value) {\n    if (!Array.isArray(value) || value.some((val) => typeof val !== type)) {\n        throw new Error(`Invalid ${name} list: ${value}`);\n    }\n}\nfunction validateObject(name, obj) {\n    if (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) {\n        throw new Error(`${name} should be an object`);\n    }\n}\nfunction validateArray(name, array) {\n    if (!Array.isArray(array)) {\n        throw new Error(`${name} should be an array`);\n    }\n}\n\nexport const UPDATE_METHODS = [\n    \"unlink\",\n    \"create\",\n    \"write\",\n    \"web_save\",\n    \"web_save_multi\",\n    \"action_archive\",\n    \"action_unarchive\",\n];\n\nexport class ORM {\n    constructor() {\n        this.rpc = rpc; // to be overridable by the SampleORM\n        /** @protected */\n        this._silent = false;\n        this._cache = false;\n    }\n\n    /** @returns {ORM} */\n    get silent() {\n        return Object.assign(Object.create(this), { _silent: true });\n    }\n\n    /**\n     * @param {object} options\n     * @returns {ORM}\n     */\n    cache(options = {}) {\n        return Object.assign(Object.create(this), { _cache: options });\n    }\n\n    /**\n     * @param {string} model\n     * @param {string} method\n     * @param {any[]} [args=[]]\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any>}\n     */\n    call(model, method, args = [], kwargs = {}) {\n        validateModel(model);\n        const url = `/web/dataset/call_kw/${model}/${method}`;\n        const fullContext = Object.assign({}, user.context, kwargs.context || {});\n        const fullKwargs = Object.assign({}, kwargs, { context: fullContext });\n        const params = {\n            model,\n            method,\n            args,\n            kwargs: fullKwargs,\n        };\n        return this.rpc(url, params, {\n            silent: this._silent,\n            cache: this._cache,\n        });\n    }\n\n    /**\n     * @param {string} model\n     * @param {any[]} records\n     * @param {any} [kwargs=[]]\n     * @returns {Promise<number>}\n     */\n    create(model, records, kwargs = {}) {\n        validateArray(\"records\", records);\n        for (const record of records) {\n            validateObject(\"record\", record);\n        }\n        return this.call(model, \"create\", [records], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {string[]} fields\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    read(model, ids, fields, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        if (fields) {\n            validatePrimitiveList(\"fields\", \"string\", fields);\n        }\n        if (!ids.length) {\n            return Promise.resolve([]);\n        }\n        return this.call(model, \"read\", [ids, fields], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {string[]} fields\n     * @param {string[]} groupby\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    formattedReadGroup(model, domain, groupby, aggregates, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        validatePrimitiveList(\"groupby\", \"string\", groupby);\n        validatePrimitiveList(\"aggregates\", \"string\", aggregates);\n        return this.call(model, \"formatted_read_group\", [], {\n            domain,\n            groupby,\n            aggregates,\n            ...kwargs,\n        }).then((res) => {\n            for (const group of res) {\n                group[\"__domain\"] = Domain.and([domain, group[\"__extra_domain\"]]).toList();\n            }\n            return res;\n        });\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {string[]} fields\n     * @param {string[][]} grouping_sets\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    formattedReadGroupingSets(model, domain, grouping_sets, aggregates, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        validateArray(\"groupby\", grouping_sets);\n        validatePrimitiveList(\"aggregates\", \"string\", aggregates);\n        return this.call(model, \"formatted_read_grouping_sets\", [], {\n            domain,\n            grouping_sets,\n            aggregates,\n            ...kwargs,\n        }).then((res) => {\n            for (const groups of res) {\n                for (const group of groups) {\n                    group[\"__domain\"] = Domain.and([domain, group[\"__extra_domain\"]]).toList();\n                }\n            }\n            return res;\n        });\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    search(model, domain, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        return this.call(model, \"search\", [domain], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {string[]} fields\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    searchRead(model, domain, fields, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        if (fields) {\n            validatePrimitiveList(\"fields\", \"string\", fields);\n        }\n        return this.call(model, \"search_read\", [], { ...kwargs, domain, fields });\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {any} [kwargs={}]\n     * @returns {Promise<number>}\n     */\n    searchCount(model, domain, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        return this.call(model, \"search_count\", [domain], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {any} [kwargs={}]\n     * @returns {Promise<boolean>}\n     */\n    unlink(model, ids, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        if (!ids.length) {\n            return Promise.resolve(true);\n        }\n        return this.call(model, \"unlink\", [ids], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {string[]} groupby\n     * @param {string[]} aggregates\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    webReadGroup(model, domain, groupby, aggregates, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        validatePrimitiveList(\"aggregates\", \"string\", aggregates);\n        return this.call(model, \"web_read_group\", [], {\n            domain,\n            groupby,\n            aggregates,\n            ...kwargs,\n        });\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {any} [kwargs={}]\n     * @param {Object} [kwargs.specification]\n     * @param {Object} [kwargs.context]\n     * @returns {Promise<any[]>}\n     */\n    webRead(model, ids, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        return this.call(model, \"web_read\", [ids], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {object} [kwargs={}]\n     * @param {object} [kwargs.context]\n     * @param {string} [kwargs.field_name]\n     * @param {number} [kwargs.offset]\n     * @param {object} [kwargs.specification]\n     * @returns {Promise<any[]>}\n     */\n    webResequence(model, ids, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        return this.call(model, \"web_resequence\", [ids], {\n            ...kwargs,\n            specification: kwargs.specification || {},\n        });\n    }\n\n    /**\n     * @param {string} model\n     * @param {import(\"@web/core/domain\").DomainListRepr} domain\n     * @param {any} [kwargs={}]\n     * @returns {Promise<any[]>}\n     */\n    webSearchRead(model, domain, kwargs = {}) {\n        validateArray(\"domain\", domain);\n        return this.call(model, \"web_search_read\", [], { ...kwargs, domain });\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {any} data\n     * @param {any} [kwargs={}]\n     * @returns {Promise<boolean>}\n     */\n    write(model, ids, data, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        validateObject(\"data\", data);\n        return this.call(model, \"write\", [ids, data], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {any} data\n     * @param {any} [kwargs={}]\n     * @param {Object} [kwargs.specification]\n     * @param {Object} [kwargs.context]\n     * @returns {Promise<any[]>}\n     */\n    webSave(model, ids, data, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        validateObject(\"data\", data);\n        return this.call(model, \"web_save\", [ids, data], kwargs);\n    }\n\n    /**\n     * @param {string} model\n     * @param {number[]} ids\n     * @param {Object[]} data\n     * @param {Object} [kwargs={}]\n     * @param {Object} [kwargs.specification]\n     * @param {Object} [kwargs.context]\n     * @returns {Promise<any[]>}\n     */\n    async webSaveMulti(model, ids, data, kwargs = {}) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        validateArray(\"data\", data);\n        data.forEach((d) => {\n            validateObject(\"data item\", d);\n        });\n        return this.call(model, \"web_save_multi\", [ids, data], kwargs);\n    }\n}\n\n/**\n * Note:\n *\n * To hide RPC errors, use the following API:\n *\n * this.orm = useService('orm');\n * ...\n * const result = await this.orm.silent.read('res.partner', [id]);\n */\nexport const ormService = {\n    async: [\n        \"call\",\n        \"create\",\n        \"nameGet\",\n        \"read\",\n        \"formattedReadGroup\",\n        \"webReadGroup\",\n        \"search\",\n        \"searchRead\",\n        \"unlink\",\n        \"webResequence\",\n        \"webSearchRead\",\n        \"write\",\n    ],\n    start() {\n        return new ORM();\n    },\n};\n\nregistry.category(\"services\").add(\"orm\", ormService);\n", "import { Component, onWillDestroy, useChildSubEnv, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { sortBy } from \"@web/core/utils/arrays\";\nimport { ErrorHandler } from \"@web/core/utils/components\";\n\nconst OVERLAY_ITEMS = [];\nexport const OVERLAY_SYMBOL = Symbol(\"Overlay\");\n\nclass OverlayItem extends Component {\n    static template = \"web.OverlayContainer.Item\";\n    static components = {};\n    static props = {\n        component: { type: Function },\n        props: { type: Object },\n        env: { type: Object, optional: true },\n    };\n\n    setup() {\n        this.rootRef = useRef(\"rootRef\");\n\n        OVERLAY_ITEMS.push(this);\n        onWillDestroy(() => {\n            const index = OVERLAY_ITEMS.indexOf(this);\n            OVERLAY_ITEMS.splice(index, 1);\n        });\n\n        if (this.props.env) {\n            this.__owl__.childEnv = this.props.env;\n        }\n\n        useChildSubEnv({\n            [OVERLAY_SYMBOL]: {\n                contains: (target) => this.contains(target),\n            },\n        });\n    }\n\n    get subOverlays() {\n        return OVERLAY_ITEMS.slice(OVERLAY_ITEMS.indexOf(this));\n    }\n\n    contains(target) {\n        return (\n            this.rootRef.el?.contains(target) ||\n            this.subOverlays.some((oi) => oi.rootRef.el?.contains(target))\n        );\n    }\n}\n\nexport class OverlayContainer extends Component {\n    static template = \"web.OverlayContainer\";\n    static components = { ErrorHandler, OverlayItem };\n    static props = { overlays: Object };\n\n    setup() {\n        this.root = useRef(\"root\");\n        this.state = useState({ rootEl: null });\n        useEffect(\n            () => {\n                this.state.rootEl = this.root.el;\n            },\n            () => [this.root.el]\n        );\n    }\n\n    get sortedOverlays() {\n        return sortBy(Object.values(this.props.overlays), (overlay) => overlay.sequence);\n    }\n\n    isVisible(overlay) {\n        return overlay.rootId === this.state.rootEl?.getRootNode()?.host?.id;\n    }\n\n    handleError(overlay, error) {\n        overlay.remove();\n        Promise.resolve().then(() => {\n            throw error;\n        });\n    }\n}\n", "import { markRaw, reactive } from \"@odoo/owl\";\nimport { registry } from \"../registry\";\nimport { OverlayContainer } from \"./overlay_container\";\n\nconst mainComponents = registry.category(\"main_components\");\nconst services = registry.category(\"services\");\n\n/**\n * @typedef {{\n *  env?: object;\n *  onRemove?: () => void;\n *  sequence?: number;\n *  rootId?: string;\n * }} OverlayServiceAddOptions\n */\n\nexport const overlayService = {\n    start() {\n        let nextId = 0;\n        const overlays = reactive({});\n\n        mainComponents.add(\"OverlayContainer\", {\n            Component: OverlayContainer,\n            props: { overlays },\n        });\n\n        const remove = async (id, onRemove = () => {}, removeParams) => {\n            if (id in overlays) {\n                await onRemove(removeParams);\n                delete overlays[id];\n            }\n        };\n\n        /**\n         * @param {typeof Component} component\n         * @param {object} props\n         * @param {OverlayServiceAddOptions} [options]\n         * @returns {() => void}\n         */\n        const add = (component, props, options = {}) => {\n            const id = ++nextId;\n            const removeCurrentOverlay = (removeParams) =>\n                remove(id, options.onRemove, removeParams);\n            overlays[id] = {\n                id,\n                component,\n                env: options.env && markRaw(options.env),\n                props,\n                remove: removeCurrentOverlay,\n                sequence: options.sequence ?? 50,\n                rootId: options.rootId,\n            };\n            return removeCurrentOverlay;\n        };\n\n        return { add, overlays };\n    },\n};\n\nservices.add(\"overlay\", overlayService);\n", "import { useAutofocus } from \"../utils/hooks\";\nimport { clamp } from \"../utils/numbers\";\n\nimport { Component, EventBus, useEffect, useExternalListener, useState } from \"@odoo/owl\";\n\nexport const PAGER_UPDATED_EVENT = \"PAGER:UPDATED\";\nexport const pagerBus = new EventBus();\n\n/**\n * Pager\n *\n * The pager goes from 1 to total (included).\n * The current value is minimum if limit === 1 or the interval:\n *      [minimum, minimum + limit[ if limit > 1].\n * The value can be manually changed by clicking on the pager value and giving\n * an input matching the pattern: min[,max] (in which the comma can be a dash\n * or a semicolon).\n * The pager also provides two buttons to quickly change the current page (next\n * or previous).\n * @extends Component\n */\nexport class Pager extends Component {\n    static template = \"web.Pager\";\n    static defaultProps = {\n        isEditable: true,\n        withAccessKey: true,\n    };\n    static props = {\n        offset: Number,\n        limit: Number,\n        total: Number,\n        onUpdate: Function,\n        isEditable: { type: Boolean, optional: true },\n        withAccessKey: { type: Boolean, optional: true },\n        updateTotal: { type: Function, optional: true },\n    };\n\n    setup() {\n        this.state = useState({\n            isEditing: false,\n            isDisabled: false,\n        });\n        this.inputRef = useAutofocus();\n        useExternalListener(document, \"mousedown\", this.onClickAway, { capture: true });\n        let firstMount = true;\n        useEffect(\n            () => {\n                if (!firstMount && this.env.isSmall) {\n                    pagerBus.trigger(PAGER_UPDATED_EVENT, {\n                        value: this.value,\n                        total: this.props.total,\n                    });\n                }\n                firstMount = false;\n            },\n            () => [this.props.offset, this.props.limit, this.props.total]\n        );\n    }\n\n    /**\n     * @returns {number}\n     */\n    get minimum() {\n        return this.props.offset + 1;\n    }\n    /**\n     * @returns {number}\n     */\n    get maximum() {\n        return Math.min(this.props.offset + this.props.limit, this.props.total);\n    }\n    /**\n     * @returns {string}\n     */\n    get value() {\n        const parts = [this.minimum];\n        if (this.props.limit > 1) {\n            parts.push(this.maximum);\n        }\n        return parts.join(\"-\");\n    }\n    /**\n     * Note: returns false if we received the props \"updateTotal\", as in this case we don't know\n     * the real total so we can't assert that there's a single page.\n     * @returns {boolean} true if there is only one page\n     */\n    get isSinglePage() {\n        return !this.props.updateTotal && this.minimum === 1 && this.maximum === this.props.total;\n    }\n    /**\n     * @param {-1 | 1} direction\n     */\n    async navigate(direction) {\n        let minimum = this.props.offset + this.props.limit * direction;\n        let total = this.props.total;\n        if (this.props.updateTotal && minimum < 0) {\n            // we must know the real total to be able to loop by doing \"previous\"\n            total = await this.props.updateTotal();\n        }\n        if (minimum >= total) {\n            if (!this.props.updateTotal) {\n                // only loop forward if we know the real total, otherwise let the minimum\n                // go out of range\n                minimum = 0;\n            }\n        } else if (minimum < 0 && this.props.limit === 1) {\n            minimum = total - 1;\n        } else if (minimum < 0 && this.props.limit > 1) {\n            minimum = total - (total % this.props.limit || this.props.limit);\n        }\n        this.update(minimum, this.props.limit, true);\n    }\n    /**\n     * @param {string} value\n     * @returns {{ minimum: number, maximum: number }}\n     */\n    async parse(value) {\n        let [minimum, maximum] = value.trim().split(/\\s*[-\\s,;]\\s*/);\n        minimum = parseInt(minimum, 10);\n        maximum = maximum ? parseInt(maximum, 10) : minimum;\n        if (this.props.updateTotal) {\n            // we don't know the real total, so we can't clamp\n            return { minimum: minimum - 1, maximum };\n        }\n        return {\n            minimum: clamp(minimum, 1, this.props.total) - 1,\n            maximum: clamp(maximum, 1, this.props.total),\n        };\n    }\n    /**\n     * @param {string} value\n     */\n    async setValue(value) {\n        const { minimum, maximum } = await this.parse(value);\n\n        if (!isNaN(minimum) && !isNaN(maximum) && minimum < maximum) {\n            this.update(minimum, maximum - minimum);\n        }\n    }\n    /**\n     * @param {number} offset\n     * @param {number} limit\n     * @param {Boolean} hasNavigated\n     */\n    async update(offset, limit, hasNavigated) {\n        this.state.isDisabled = true;\n        try {\n            await this.props.onUpdate({ offset, limit }, hasNavigated);\n        } finally {\n            this.state.isDisabled = false;\n            this.state.isEditing = false;\n        }\n    }\n\n    async updateTotal() {\n        if (!this.state.isDisabled) {\n            this.state.isDisabled = true;\n            await this.props.updateTotal();\n            this.state.isDisabled = false;\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onClickAway(ev) {\n        if (ev.target !== this.inputRef.el) {\n            this.state.isEditing = false;\n        }\n    }\n    onInputBlur() {\n        this.state.isEditing = false;\n    }\n    /**\n     * @param {Event} ev\n     */\n    onInputChange(ev) {\n        this.setValue(ev.target.value);\n        if (!this.state.isDisabled) {\n            ev.preventDefault();\n        }\n    }\n    /**\n     * @param {KeyboardEvent} ev\n     */\n    onInputKeydown(ev) {\n        switch (ev.key) {\n            case \"Enter\":\n                ev.preventDefault();\n                ev.stopPropagation();\n                this.setValue(ev.currentTarget.value);\n                break;\n            case \"Escape\":\n                ev.preventDefault();\n                ev.stopPropagation();\n                this.state.isEditing = false;\n                break;\n        }\n    }\n    onValueClick() {\n        if (this.props.isEditable && !this.state.isEditing && !this.state.isDisabled) {\n            if (this.inputRef.el) {\n                this.inputRef.el.focus();\n            }\n            this.state.isEditing = true;\n        }\n    }\n}\n", "import { browser } from \"../browser/browser\";\nimport { registry } from \"../registry\";\nimport { Transition } from \"../transition\";\nimport { useBus } from \"../utils/hooks\";\n\nimport { Component, useState } from \"@odoo/owl\";\nimport { PAGER_UPDATED_EVENT, pagerBus } from \"./pager\";\n\nexport class PagerIndicator extends Component {\n    static template = \"web.PagerIndicator\";\n    static components = { Transition };\n    static props = {};\n\n    setup() {\n        this.state = useState({\n            show: false,\n            value: \"-\",\n            total: 0,\n        });\n        this.startShowTimer = null;\n        useBus(pagerBus, PAGER_UPDATED_EVENT, this.pagerUpdate);\n    }\n\n    pagerUpdate({ detail }) {\n        this.state.value = detail.value;\n        this.state.total = detail.total;\n        browser.clearTimeout(this.startShowTimer);\n        this.state.show = true;\n        this.startShowTimer = browser.setTimeout(() => {\n            this.state.show = false;\n        }, 1400);\n    }\n}\n\nregistry.category(\"main_components\").add(\"PagerIndicator\", {\n    Component: PagerIndicator,\n});\n", "import { Component, onMounted, onWillDestroy, useRef } from \"@odoo/owl\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { OVERLAY_SYMBOL } from \"@web/core/overlay/overlay_container\";\nimport { usePosition } from \"@web/core/position/position_hook\";\nimport { reverseForRTL } from \"@web/core/position/utils\";\nimport { useActiveElement } from \"@web/core/ui/ui_service\";\nimport { mergeClasses } from \"@web/core/utils/classname\";\nimport { useForwardRefToParent } from \"@web/core/utils/hooks\";\n\n/**\n * @param {EventTarget} target\n * @param {keyof HTMLElementEventMap | keyof WindowEventMap} eventName\n * @param {(ev: Event) => any} handler\n * @param {EventInit} [eventParams]\n */\nfunction useEarlyExternalListener(target, eventName, handler, eventParams) {\n    target.addEventListener(eventName, handler, eventParams);\n    onWillDestroy(() => target.removeEventListener(eventName, handler, eventParams));\n}\n\n/**\n * Will trigger the callback when the window is clicked, giving\n * the clicked element as parameter.\n *\n * This also handles the case where an iframe is clicked.\n *\n * @param {(node?: Node) => any} callback\n */\nfunction useClickAway(callback) {\n    function blurHandler(ev) {\n        const target = ev.relatedTarget || document.activeElement;\n        if (target?.tagName === \"IFRAME\") {\n            callback(target);\n        }\n    }\n\n    function navigationHandler() {\n        callback(document.documentElement);\n    }\n\n    function pointerDownHandler(ev) {\n        callback(ev.composedPath()[0]);\n    }\n\n    useEarlyExternalListener(window, \"pointerdown\", pointerDownHandler, { capture: true });\n    useEarlyExternalListener(window, \"blur\", blurHandler, { capture: true });\n    useEarlyExternalListener(window, \"popstate\", navigationHandler, { capture: true });\n}\n\nconst POPOVERS = new WeakMap();\n/**\n * Can be used to retrieve the popover element for a given target.\n * @param {HTMLElement} target\n * @returns {HTMLElement | undefined} the popover element if it exists\n */\nexport function getPopoverForTarget(target) {\n    return POPOVERS.get(target);\n}\n\nexport class Popover extends Component {\n    static template = \"web.Popover\";\n    static defaultProps = {\n        animation: true,\n        arrow: true,\n        class: \"\",\n        closeOnClickAway: () => true,\n        closeOnEscape: true,\n        componentProps: {},\n        fixedPosition: false,\n        position: \"bottom\",\n        setActiveElement: false,\n    };\n    static props = {\n        // Main props\n        component: { type: Function },\n        componentProps: { optional: true, type: Object },\n        target: {\n            validate: (target) => {\n                // target may be inside an iframe, so get the Element constructor\n                // to test against from its owner document's default view\n                const Element = target?.ownerDocument?.defaultView?.Element;\n                return (\n                    (Boolean(Element) &&\n                        (target instanceof Element || target instanceof window.Element)) ||\n                    (typeof target === \"object\" && target?.constructor?.name?.endsWith(\"Element\"))\n                );\n            },\n        },\n        close: { type: Function },\n\n        // Styling and semantical props\n        animation: { optional: true, type: Boolean },\n        arrow: { optional: true, type: Boolean },\n        class: { optional: true },\n        role: { optional: true, type: String },\n\n        // Positioning props\n        fixedPosition: { optional: true, type: Boolean },\n        extendedFlipping: { optional: true, type: Boolean },\n        holdOnHover: { optional: true, type: Boolean },\n        onPositioned: { optional: true, type: Function },\n        position: {\n            optional: true,\n            type: String,\n            validate: (p) => {\n                const [d, v = \"middle\"] = p.split(\"-\");\n                return (\n                    [\"top\", \"bottom\", \"left\", \"right\"].includes(d) &&\n                    [\"start\", \"middle\", \"end\", \"fit\"].includes(v)\n                );\n            },\n        },\n\n        // Control props\n        closeOnClickAway: { optional: true, type: Function },\n        closeOnEscape: { optional: true, type: Boolean },\n        setActiveElement: { optional: true, type: Boolean },\n\n        // Technical props\n        ref: { optional: true, type: Function },\n        slots: { optional: true, type: Object },\n    };\n    static animationTime = 200;\n\n    setup() {\n        if (this.props.setActiveElement) {\n            useActiveElement(\"ref\");\n        }\n\n        useForwardRefToParent(\"ref\");\n        this.popoverRef = useRef(\"ref\");\n        this.position = usePosition(\"ref\", () => this.props.target, this.positioningOptions);\n\n        const resizeObserver = new ResizeObserver(() => {\n            if (!this.props.fixedPosition && this.animationDone) {\n                this.position.unlock();\n            }\n        });\n\n        onMounted(() => {\n            POPOVERS.set(this.props.target, this.popoverRef.el);\n            resizeObserver.observe(this.popoverRef.el);\n        });\n        onWillDestroy(() => POPOVERS.delete(this.props.target));\n\n        if (this.props.target.isConnected) {\n            useClickAway(this.onClickAway.bind(this));\n\n            if (this.props.closeOnEscape) {\n                useHotkey(\"escape\", () => this.props.close());\n            }\n            const targetObserver = new MutationObserver(this.onTargetMutate.bind(this));\n            targetObserver.observe(this.props.target.parentElement, { childList: true });\n            onWillDestroy(() => targetObserver.disconnect());\n        } else {\n            this.props.close();\n        }\n    }\n\n    get defaultClassObj() {\n        return mergeClasses(\"o_popover popover mw-100 bs-popover-auto\", this.props.class);\n    }\n\n    get positioningOptions() {\n        return {\n            extendedFlipping: this.props.extendedFlipping,\n            margin: this.props.arrow ? 8 : 0,\n            onPositioned: (el, solution) => {\n                this.onPositioned(solution);\n                this.props.onPositioned?.(el, solution);\n            },\n            position: this.props.position,\n            shrink: true,\n        };\n    }\n\n    animate(direction) {\n        const transform = {\n            top: [\"translateY(-5%)\", \"translateY(0)\"],\n            right: [\"translateX(5%)\", \"translateX(0)\"],\n            bottom: [\"translateY(5%)\", \"translateY(0)\"],\n            left: [\"translateX(-5%)\", \"translateX(0)\"],\n        }[direction];\n        return this.popoverRef.el.animate(\n            { opacity: [0, 1], transform },\n            this.constructor.animationTime\n        );\n    }\n\n    isInside(target) {\n        return (\n            this.props.target?.contains(target) ||\n            this.popoverRef?.el?.contains(target) ||\n            this.env[OVERLAY_SYMBOL]?.contains(target)\n        );\n    }\n\n    onClickAway(target) {\n        if (this.props.closeOnClickAway(target) && !this.isInside(target)) {\n            this.props.close();\n        }\n    }\n\n    onPositioned({ direction, variant, variantOffset }) {\n        if (this.props.arrow) {\n            this.updateArrow(direction, variant, variantOffset);\n        }\n\n        // opening animation (only once)\n        if (this.props.animation && !this.animationDone) {\n            this.position.lock();\n            this.animate(direction).finished.then(() => {\n                this.animationDone = true;\n                if (!this.props.fixedPosition) {\n                    this.position.unlock();\n                }\n            });\n        }\n\n        if (this.props.fixedPosition) {\n            // Prevent further positioning updates if fixed position is wanted\n            this.position.lock();\n        }\n    }\n\n    onTargetMutate() {\n        if (!this.props.target.isConnected) {\n            this.props.close();\n        }\n    }\n\n    updateArrow(direction, variant, variantOffset) {\n        const { el } = this.popoverRef;\n\n        // Reverse the direction if RTL as bootstrap expects it that way\n        [direction, variant] = reverseForRTL(direction, variant);\n\n        // Update the bootstrap popper placement, in order to give the arrow its shape\n        el.dataset.popperPlacement = direction;\n\n        // Update arrow position\n        const vertical = [\"top\", \"bottom\"].includes(direction);\n        const placementProperty = vertical ? \"left\" : \"top\";\n        const placement = {\n            start: \"--position-min\",\n            middle: \"--position-center\",\n            fit: \"--position-center\",\n            end: \"--position-max\",\n        }[variant];\n        const arrowEl = el.querySelector(\":scope > .popover-arrow\");\n        Object.assign(arrowEl.style, {\n            top: \"\",\n            left: \"\",\n            [placementProperty]: `clamp(\n                var(--position-min),\n                calc(var(${placement}) - ${variantOffset}px),\n                var(--position-max)\n            )`,\n        });\n\n        // Should the arrow get sucked?\n        const sizeProperty = vertical ? \"width\" : \"height\";\n        const { [sizeProperty]: arrowSize, [placementProperty]: arrowPosition } =\n            arrowEl.getBoundingClientRect();\n        const { [sizeProperty]: targetSize, [placementProperty]: targetPosition } =\n            this.props.target.getBoundingClientRect();\n        const arrowCenter = arrowPosition + arrowSize / 2;\n        const margin = arrowSize / 2 - 1;\n        const hasEnoughSpace = arrowSize < targetSize - 2 * margin;\n        const isOutsideSafeEdge =\n            arrowCenter < targetPosition + margin ||\n            arrowCenter > targetPosition + targetSize - margin;\n        arrowEl.classList.toggle(\"sucked\", hasEnoughSpace && isOutsideSafeEdge);\n    }\n}\n", "import { onWillUnmount, status, useComponent } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\n\n/**\n * @typedef {import(\"@web/core/popover/popover_service\").PopoverServiceAddFunction} PopoverServiceAddFunction\n * @typedef {import(\"@web/core/popover/popover_service\").PopoverServiceAddOptions} PopoverServiceAddOptions\n */\n\n/**\n * @typedef PopoverHookReturnType\n * @property {(target: string | HTMLElement, props: object) => void} open\n *  - Signals the manager to open the configured popover\n *    component on the target, with the given props.\n * @property {() => void} close\n *  - Signals the manager to remove the popover.\n * @property {boolean} isOpen\n *  - Whether the popover is currently open.\n */\n\n/**\n * @param {PopoverServiceAddFunction} addFn\n * @param {typeof import(\"@odoo/owl\").Component} component\n * @param {PopoverServiceAddOptions} options\n * @returns {PopoverHookReturnType}\n */\nexport function makePopover(addFn, component, options) {\n    let removeFn = null;\n    function close() {\n        removeFn?.();\n    }\n    return {\n        open(target, props) {\n            close();\n            const newOptions = Object.create(options);\n            newOptions.onClose = () => {\n                removeFn = null;\n                options.onClose?.();\n            };\n            removeFn = addFn(target, component, props, newOptions);\n        },\n        close,\n        get isOpen() {\n            return Boolean(removeFn);\n        },\n    };\n}\n\n/**\n * Manages a component to be used as a popover.\n *\n * @param {typeof import(\"@odoo/owl\").Component} component\n * @param {PopoverServiceAddOptions} [options]\n * @returns {PopoverHookReturnType}\n */\nexport function usePopover(component, options = {}) {\n    let service;\n    if (options.useBottomSheet) {\n        service = useService(\"bottom_sheet\");\n    } else {\n        service = useService(\"popover\");\n    }\n    const owner = useComponent();\n    const newOptions = Object.create(options);\n    newOptions.onClose = () => {\n        if (status(owner) !== \"destroyed\") {\n            options.onClose?.();\n        }\n    };\n    const popover = makePopover(service.add, component, newOptions);\n    onWillUnmount(popover.close);\n    return popover;\n}\n", "import { markRaw } from \"@odoo/owl\";\nimport { Popover } from \"@web/core/popover/popover\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * @typedef {{\n *   animation?: Boolean;\n *   arrow?: Boolean;\n *   closeOnClickAway?: boolean | (target: HTMLElement) => boolean;\n *   closeOnEscape?: boolean;\n *   env?: object;\n *   fixedPosition?: boolean;\n *   onClose?: () => void;\n *   onPositioned?: import(\"@web/core/position/position_hook\").UsePositionOptions[\"onPositioned\"];\n *   popoverClass?: string;\n *   role?: string;\n *   position?: import(\"@web/core/position/position_hook\").UsePositionOptions[\"position\"];\n *   ref?: Function;\n * }} PopoverServiceAddOptions\n *\n * @typedef {ReturnType<popoverService[\"start\"]>[\"add\"]} PopoverServiceAddFunction\n */\n\nexport const popoverService = {\n    dependencies: [\"overlay\"],\n    start(_, { overlay }) {\n        /**\n         * Signals the manager to add a popover.\n         *\n         * @param {HTMLElement} target\n         * @param {typeof import(\"@odoo/owl\").Component} component\n         * @param {object} [props]\n         * @param {PopoverServiceAddOptions} [options]\n         * @returns {() => void}\n         */\n        const add = (target, component, props = {}, options = {}) => {\n            const closeOnClickAway =\n                typeof options.closeOnClickAway === \"function\"\n                    ? options.closeOnClickAway\n                    : () => options.closeOnClickAway ?? true;\n            const remove = overlay.add(\n                Popover,\n                {\n                    target,\n                    close: () => remove(),\n                    closeOnClickAway,\n                    closeOnEscape: options.closeOnEscape,\n                    component,\n                    componentProps: markRaw(props),\n                    extendedFlipping: options.extendedFlipping,\n                    ref: options.ref,\n                    class: options.popoverClass,\n                    animation: options.animation,\n                    arrow: options.arrow,\n                    role: options.role,\n                    position: options.position,\n                    onPositioned: options.onPositioned,\n                    fixedPosition: options.fixedPosition,\n                    holdOnHover: options.holdOnHover,\n                    setActiveElement: options.setActiveElement ?? true,\n                },\n                {\n                    env: options.env,\n                    onRemove: options.onClose,\n                    rootId: target.getRootNode()?.host?.id,\n                }\n            );\n\n            return remove;\n        };\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"popover\", popoverService);\n", "import { reposition } from \"@web/core/position/utils\";\nimport { omit } from \"@web/core/utils/objects\";\nimport { useThrottleForAnimation } from \"@web/core/utils/timing\";\nimport {\n    EventBus,\n    onWillDestroy,\n    useChildSubEnv,\n    useComponent,\n    useEffect,\n    useRef,\n} from \"@odoo/owl\";\n\n/**\n * @typedef {import(\"@web/core/position/utils\").ComputePositionOptions} ComputePositionOptions\n * @typedef {import(\"@web/core/position/utils\").PositioningSolution} PositioningSolution\n *\n * @typedef {Object} UsePositionOptionsExtensionType\n * @property {(popperElement: HTMLElement, solution: PositioningSolution) => void} [onPositioned]\n *  callback called when the positioning is done.\n * @typedef {ComputePositionOptions & UsePositionOptionsExtensionType} UsePositionOptions\n *\n * @typedef PositioningControl\n * @property {() => void} lock prevents further positioning updates\n * @property {() => void} unlock allows further positioning updates (triggers an update right away)\n */\n\nexport const POSITION_BUS = Symbol(\"position-bus\");\n\n/**\n * Makes sure that the `popper` element is always\n * placed at `position` from the `target` element.\n * If doing so the `popper` element is clipped off `container`,\n * sensible fallback positions are tried.\n * If all of fallback positions are also clipped off `container`,\n * the original position is used.\n *\n * Note: The popper element should be indicated in your template\n *       with a t-ref reference matching the refName argument.\n *\n * @param {string} refName\n *  name of the reference to the popper element in the template.\n * @param {() => HTMLElement} getTarget\n * @param {UsePositionOptions} [options={}] the options to be used for positioning\n * @returns {PositioningControl}\n *  control object to lock/unlock the positioning.\n */\nexport function usePosition(refName, getTarget, options = {}) {\n    const ref = useRef(refName);\n    let lock = false;\n    const update = () => {\n        const targetEl = getTarget();\n        if (!ref.el || !targetEl?.isConnected || lock) {\n            // No compute needed\n            return;\n        }\n        const repositionOptions = omit(options, \"onPositioned\");\n        const solution = reposition(ref.el, targetEl, repositionOptions);\n        options.position = `${solution.direction}-${solution.variant}`; // memorize last position\n        options.onPositioned?.(ref.el, solution);\n    };\n\n    const component = useComponent();\n    const bus = component.env[POSITION_BUS] || new EventBus();\n\n    let executingUpdate = false;\n    const batchedUpdate = async () => {\n        // not same as batch, here we're executing once and then awaiting\n        if (!executingUpdate) {\n            executingUpdate = true;\n            update();\n            await Promise.resolve();\n            executingUpdate = false;\n        }\n    };\n    bus.addEventListener(\"update\", batchedUpdate);\n    onWillDestroy(() => bus.removeEventListener(\"update\", batchedUpdate));\n\n    const isTopmost = !(POSITION_BUS in component.env);\n    if (isTopmost) {\n        useChildSubEnv({ [POSITION_BUS]: bus });\n    }\n\n    const throttledUpdate = useThrottleForAnimation(() => bus.trigger(\"update\"));\n    useEffect(() => {\n        // Reposition\n        bus.trigger(\"update\");\n\n        if (isTopmost) {\n            // Attach listeners to keep the positioning up to date\n            const scrollListener = (e) => {\n                if (ref.el?.contains(e.target)) {\n                    // In case the scroll event occurs inside the popper, do not reposition\n                    return;\n                }\n                throttledUpdate();\n            };\n            // Get the ownerDocument of the target, and the topmost document\n            // if the target is inside an iframe of same-origin\n            // (c.f. html_builder), to handle scroll events at these 2 levels.\n            const documents = [];\n            const targetDocument = getTarget()?.ownerDocument;\n            if (targetDocument) {\n                documents.push(targetDocument);\n                if (\n                    targetDocument.defaultView &&\n                    targetDocument.defaultView.top !== targetDocument.defaultView\n                ) {\n                    try {\n                        documents.push(targetDocument.defaultView.top.document);\n                    } catch {\n                        // Don't access the top document if it is not allowed.\n                        // (i.e. iframe origin or sandbox restriction)\n                    }\n                }\n            }\n            for (const document of documents) {\n                document.addEventListener(\"scroll\", scrollListener, { capture: true });\n                document.addEventListener(\"load\", throttledUpdate, { capture: true });\n            }\n            window.addEventListener(\"resize\", throttledUpdate);\n            return () => {\n                for (const document of documents) {\n                    document.removeEventListener(\"scroll\", scrollListener, { capture: true });\n                    document.removeEventListener(\"load\", throttledUpdate, { capture: true });\n                }\n                window.removeEventListener(\"resize\", throttledUpdate);\n            };\n        }\n    });\n\n    return {\n        lock: () => {\n            lock = true;\n        },\n        unlock: () => {\n            lock = false;\n            bus.trigger(\"update\");\n        },\n    };\n}\n", "import { localization } from \"@web/core/l10n/localization\";\n\n/**\n * @typedef {\"top\" | \"left\" | \"bottom\" | \"right\"} Direction\n * @typedef {\"start\" | \"middle\" | \"end\" | \"fit\"} Variant\n *\n * @typedef {{[direction in Direction]: string}} DirectionFlipOrder\n *  string values should match regex /^[tbrl]+$/m\n *\n * @typedef {{[variant in Variant]: string}} VariantFlipOrder\n *  string values should match regex /^[smef]+$/m\n *\n * @typedef {{\n *  top: number,\n *  left: number,\n *  maxHeight?: number;\n *  direction: Direction,\n *  variant: Variant,\n *  variantOffset?: number,\n * }} PositioningSolution\n *\n * @typedef ComputePositionOptions\n * @property {HTMLElement | () => HTMLElement} [container] container element\n * @property {number} [margin=0]\n *  margin in pixels between the popper and the target.\n * @property {Direction | `${Direction}-${Variant}`} [position=\"bottom\"]\n *  position of the popper relative to the target\n * @property {boolean} [flip=true]\n *  allow the popper to try a flipped direction when it overflows the container\n * @property {boolean} [extendedFlipping=false]\n *  allow the popper to try for all possible flipping directions (including center)\n *  when it overflows the container\n * @property {boolean} [shrink=false]\n *  reduce the popper's height when it overflows the container\n */\n\n/** @type {ComputePositionOptions} */\nconst DEFAULTS = {\n    flip: true,\n    margin: 0,\n    position: \"bottom\",\n};\n\n/** @type {{[d: string]: Direction}} */\nconst DIRECTIONS = { t: \"top\", r: \"right\", b: \"bottom\", l: \"left\", c: \"center\" };\n/** @type {{[v: string]: Variant}} */\nconst VARIANTS = { s: \"start\", m: \"middle\", e: \"end\", f: \"fit\" };\n/** @type DirectionFlipOrder */\nconst DIRECTION_FLIP_ORDER = { top: \"tb\", right: \"rl\", bottom: \"bt\", left: \"lr\", center: \"c\" };\n/** @type DirectionFlipOrder */\nconst EXTENDED_DIRECTION_FLIP_ORDER = {\n    top: \"tbrlc\",\n    right: \"rlbtc\",\n    bottom: \"btrlc\",\n    left: \"lrbtc\",\n    center: \"c\",\n};\n/** @type VariantFlipOrder */\nconst VARIANT_FLIP_ORDER = { start: \"se\", middle: \"m\", end: \"es\", fit: \"f\" };\n\n/**\n * @param {HTMLElement} popperEl\n * @param {HTMLElement} targetEl\n * @returns {HTMLIFrameElement?}\n */\nfunction getIFrame(popperEl, targetEl) {\n    return [...popperEl.ownerDocument.getElementsByTagName(\"iframe\")].find((iframe) =>\n        iframe.contentDocument?.contains(targetEl)\n    );\n}\n\n/**\n * Returns the RTl adapted direction and variant if needed.\n * If the current localization direction is \"rtl\":\n *  - Direction \"left\" and \"right\" are flipped to \"right\" and \"left\".\n *  - Variant \"start\" and \"end\" are flipped to \"end\" and \"start\".\n *\n * @param {Direction} direction\n * @param {Variant} [variant=\"middle\"] (default value is \"middle\")\n * @returns {[Direction, Variant]}\n */\nexport function reverseForRTL(direction, variant = \"middle\") {\n    if (localization.direction === \"rtl\") {\n        if ([\"left\", \"right\"].includes(direction)) {\n            direction = direction === \"left\" ? \"right\" : \"left\";\n        } else if ([\"start\", \"end\"].includes(variant)) {\n            // here direction is either \"top\" or \"bottom\"\n            variant = variant === \"start\" ? \"end\" : \"start\";\n        }\n    }\n    return [direction, variant];\n}\n\n/**\n * Returns the best positioning solution staying in the container or falls back\n * to the requested position.\n * The positioning data used to determine each possible position is based on\n * the target, popper, and container sizes.\n * Particularly, a popper must not overflow the container in any direction.\n * The popper will stay at `margin` distance from its target. One could also\n * use the CSS margins of the popper element to achieve the same result.\n *\n * Pre-condition: the popper element must have a fixed positioning\n *                with top and left set to 0px.\n *\n * @param {HTMLElement} popper\n * @param {HTMLElement} target\n * @param {ComputePositionOptions} options\n * @returns {PositioningSolution} the best positioning solution, relative to\n *                                the containing block of the popper.\n *                                => can be applied to popper.style.(top|left)\n */\nfunction computePosition(\n    popper,\n    target,\n    { container, extendedFlipping, flip, margin, position, shrink }\n) {\n    // Retrieve directions and variants\n    const [direction, variant = \"middle\"] = reverseForRTL(...position.split(\"-\"));\n    let directions = [direction.at(0)];\n    if (flip) {\n        directions = extendedFlipping\n            ? EXTENDED_DIRECTION_FLIP_ORDER[direction]\n            : DIRECTION_FLIP_ORDER[direction];\n    }\n    const variants = VARIANT_FLIP_ORDER[variant];\n\n    // Retrieve container\n    if (!container) {\n        container = popper.ownerDocument.documentElement;\n    } else if (typeof container === \"function\") {\n        container = container();\n    }\n\n    if (variant === \"fit\") {\n        // make sure the popper has the desired dimensions during the computation of the position\n        const styleProperty = [\"top\", \"bottom\"].includes(direction) ? \"width\" : \"height\";\n        popper.style[styleProperty] = getComputedStyle(target)[styleProperty];\n    }\n\n    // Account for popper actual margins\n    const popperStyle = getComputedStyle(popper);\n    const { marginTop, marginLeft, marginRight, marginBottom } = popperStyle;\n    const popMargins = {\n        top: parseFloat(marginTop),\n        left: parseFloat(marginLeft),\n        right: parseFloat(marginRight),\n        bottom: parseFloat(marginBottom),\n    };\n\n    // IFrame\n    const shouldAccountForIFrame = popper.ownerDocument !== target.ownerDocument;\n    const iframe = shouldAccountForIFrame ? getIFrame(popper, target) : null;\n\n    // Boxes\n    const popBox = popper.getBoundingClientRect();\n    const targetBox = target.getBoundingClientRect();\n    const contBox = container.getBoundingClientRect();\n    const iframeBox = iframe?.getBoundingClientRect() ?? { top: 0, left: 0 };\n\n    const containerIsHTMLNode = container === container.ownerDocument.firstElementChild;\n    const containerIsInIframe =\n        shouldAccountForIFrame && target.ownerDocument === container.ownerDocument;\n\n    // Compute positioning data\n    const directionsData = {\n        t: iframeBox.top + targetBox.top - popMargins.bottom - margin - popBox.height,\n        b: iframeBox.top + targetBox.bottom + popMargins.top + margin,\n        r: iframeBox.left + targetBox.right + popMargins.left + margin,\n        l: iframeBox.left + targetBox.left - popMargins.right - margin - popBox.width,\n        c: iframeBox.top + targetBox.top + targetBox.height / 2 - popBox.height / 2,\n    };\n    const variantsData = {\n        vf: iframeBox.left + targetBox.left,\n        vs: iframeBox.left + targetBox.left + popMargins.left,\n        vm: iframeBox.left + targetBox.left + targetBox.width / 2 - popBox.width / 2,\n        ve: iframeBox.left + targetBox.right - popMargins.right - popBox.width,\n        hf: iframeBox.top + targetBox.top,\n        hs: iframeBox.top + targetBox.top + popMargins.top,\n        hm: iframeBox.top + targetBox.top + targetBox.height / 2 - popBox.height / 2,\n        he: iframeBox.top + targetBox.bottom - popMargins.bottom - popBox.height,\n    };\n\n    function getPositioningData(d, v) {\n        const [direction, variant] = reverseForRTL(DIRECTIONS[d], VARIANTS[v]);\n        const result = { direction, variant };\n        const vertical = [\"t\", \"b\", \"c\"].includes(d);\n        const variantPrefix = vertical ? \"v\" : \"h\";\n        const directionValue = directionsData[d];\n        let variantValue = variantsData[variantPrefix + v];\n        const [leftCompensation, topCompensation] = containerIsInIframe\n            ? [iframeBox.left, iframeBox.top]\n            : [0, 0];\n\n        const [directionSize, variantSize] = vertical\n            ? [popBox.height, popBox.width]\n            : [popBox.width, popBox.height];\n        let [directionMin, directionMax] = vertical\n            ? [contBox.top + topCompensation, contBox.bottom + topCompensation]\n            : [contBox.left + leftCompensation, contBox.right + leftCompensation];\n        let [variantMin, variantMax] = vertical\n            ? [contBox.left + leftCompensation, contBox.right + leftCompensation]\n            : [contBox.top + topCompensation, contBox.bottom + topCompensation];\n\n        if (containerIsHTMLNode) {\n            if (vertical) {\n                directionMin += container.scrollTop;\n                directionMax += container.scrollTop;\n            } else {\n                variantMin += container.scrollTop;\n                variantMax += container.scrollTop;\n            }\n        }\n\n        // Compute overflows\n        let directionOverflow = 0;\n        if (Math.floor(directionValue) < Math.ceil(directionMin)) {\n            directionOverflow = Math.floor(directionValue) - Math.ceil(directionMin);\n        } else if (Math.ceil(directionValue + directionSize) > Math.floor(directionMax)) {\n            directionOverflow =\n                Math.ceil(directionValue + directionSize) - Math.floor(directionMax);\n        }\n        let variantOverflow = 0;\n        if (Math.floor(variantValue) < Math.ceil(variantMin)) {\n            variantOverflow = Math.floor(variantValue) - Math.ceil(variantMin);\n        } else if (Math.ceil(variantValue + variantSize) > Math.floor(variantMax)) {\n            variantOverflow = Math.ceil(variantValue + variantSize) - Math.floor(variantMax);\n        }\n\n        // All non zero values of variantOverflow lead to the\n        // same malus value since it can be corrected by shifting\n        let malus = Math.abs(directionOverflow) + (variantOverflow && 1);\n\n        // Apply variant offset\n        variantValue -= variantOverflow;\n        result.variantOffset = -variantOverflow;\n\n        const positioning = vertical\n            ? { top: directionValue, left: variantValue }\n            : { top: variantValue, left: directionValue };\n        // Subtract the offsets of the containing block (relative to the\n        // viewport). It can be done like that because the style top and\n        // left were reset to 0px in `reposition`\n        // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n        result.top = positioning.top - popBox.top;\n        result.left = positioning.left - popBox.left;\n        if (d === \"c\") {\n            // Artificial way to say the center direction is a fallback to every other\n            // once there is a direction overflow since we can always shift the position\n            // in any direction in that case\n            malus = 1.001;\n            result.top -= directionOverflow;\n        } else if (shrink && malus) {\n            const minTop = Math.floor(!vertical && v === \"s\" ? targetBox.top : contBox.top);\n            result.top = Math.max(minTop, result.top);\n\n            let height;\n            if (vertical) {\n                height = Math.abs(targetBox[direction] - (d === \"t\" ? directionMin : directionMax));\n            } else {\n                height = {\n                    s: variantMax - targetBox.top,\n                    m: variantMax - variantMin,\n                    e: targetBox.bottom - variantMin,\n                }[v];\n            }\n            result.maxHeight = Math.floor(height);\n        }\n        return { result, malus };\n    }\n\n    // Find best solution\n    const matches = [];\n    for (const d of directions) {\n        for (const v of variants) {\n            const match = getPositioningData(d, v);\n            if (!match.malus) {\n                // A perfect position match has been found.\n                return match.result;\n            }\n            matches.push(match);\n        }\n        if (!flip) {\n            // Stop when no flip is allowed\n            break;\n        }\n    }\n    // Settle for the first match with the least malus\n    return matches.sort((a, b) => a.malus - b.malus)[0].result;\n}\n\n/**\n * Repositions the popper element relatively to the target element (according to options).\n * The positioning strategy is always a fixed positioning with top and left.\n *\n * The positioning solution is returned by the `computePosition` function.\n * It will get applied to the popper element and then returned for convenience.\n *\n * @param {HTMLElement} popper\n * @param {HTMLElement} target\n * @param {ComputePositionOptions} options\n * @returns {PositioningSolution} the applied positioning solution.\n */\nexport function reposition(popper, target, options) {\n    // Reset popper style\n    popper.style.position = \"fixed\";\n    popper.style.top = \"0px\";\n    popper.style.left = \"0px\";\n\n    // Compute positioning solution\n    const solution = computePosition(popper, target, { ...DEFAULTS, ...options });\n\n    // Apply it\n    const { top, left, maxHeight } = solution;\n    popper.style.top = `${top}px`;\n    popper.style.left = `${left}px`;\n    if (maxHeight) {\n        const existingMaxHeight = getComputedStyle(popper).maxHeight;\n        popper.style.maxHeight =\n            existingMaxHeight !== \"none\"\n                ? `min(${existingMaxHeight}, ${maxHeight}px)`\n                : `${maxHeight}px`;\n    }\n\n    return solution;\n}\n", "import { Component } from \"@odoo/owl\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { isIOS } from \"@web/core/browser/feature_detection\";\n\nexport class InstallPrompt extends Component {\n    static props = {\n        close: true,\n        onClose: { type: Function },\n    };\n    static components = {\n        Dialog,\n    };\n    static template = \"web.InstallPrompt\";\n\n    get isMobileSafari() {\n        return isIOS();\n    }\n\n    onClose() {\n        this.props.close();\n        this.props.onClose();\n    }\n}\n", "import { reactive } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport {\n    isDisplayStandalone,\n    isIOS,\n    isMacOS,\n    isBrowserSafari,\n} from \"@web/core/browser/feature_detection\";\nimport { get } from \"@web/core/network/http_service\";\nimport { registry } from \"@web/core/registry\";\nimport { InstallPrompt } from \"./install_prompt\";\n\nconst serviceRegistry = registry.category(\"services\");\n\n/* Ideally, the service would directly add the event listener. Unfortunately, it happens sometimes that\n * the browser would trigger the event before the webclient (services, components, etc.) is even ready.\n * In that case, we have to get this event as soon as possible. The service can then verify if the event\n * is already stored in this variable, or add an event listener itself, to make sure the `_handleBeforeInstallPrompt`\n * function is called at the right moment, and can give the correct information to the service.\n */\nlet BEFOREINSTALLPROMPT_EVENT;\nlet REGISTER_BEFOREINSTALLPROMPT_EVENT;\n\nbrowser.addEventListener(\"beforeinstallprompt\", (ev) => {\n    // This event is only triggered by the browser when the native prompt to install can be shown\n    // This excludes incognito tabs, as well as visiting the website while the app is installed\n    if (REGISTER_BEFOREINSTALLPROMPT_EVENT) {\n        // service has been started before the event was triggered, update the service\n        return REGISTER_BEFOREINSTALLPROMPT_EVENT(ev);\n    } else {\n        // store the event for later use\n        BEFOREINSTALLPROMPT_EVENT = ev;\n    }\n});\n\nconst pwaService = {\n    dependencies: [\"dialog\"],\n    start(env, { dialog }) {\n        let _manifest;\n        let nativePrompt;\n\n        const state = reactive({\n            canPromptToInstall: false,\n            isAvailable: false,\n            isScopedApp: browser.location.href.includes(\"/scoped_app\"),\n            isSupportedOnBrowser: false,\n            startUrl: \"/odoo\",\n            decline,\n            getManifest,\n            hasScopeBeenInstalled,\n            show,\n        });\n\n        function _getInstallationState(scope = state.startUrl) {\n            const installationState = browser.localStorage.getItem(\"pwaService.installationState\");\n            return installationState ? JSON.parse(installationState)[scope] : \"\";\n        }\n\n        function _setInstallationState(value) {\n            const ls = JSON.parse(\n                browser.localStorage.getItem(\"pwaService.installationState\") || \"{}\"\n            );\n            ls[state.startUrl] = value;\n            browser.localStorage.setItem(\"pwaService.installationState\", JSON.stringify(ls));\n        }\n\n        function _removeInstallationState() {\n            const ls = JSON.parse(browser.localStorage.getItem(\"pwaService.installationState\"));\n            delete ls[state.startUrl];\n            browser.localStorage.setItem(\"pwaService.installationState\", JSON.stringify(ls));\n        }\n\n        if (state.isScopedApp) {\n            if (browser.location.pathname === \"/scoped_app\") {\n                // Installation page, use the path parameter in the URL\n                state.startUrl = \"/\" + new URL(browser.location.href).searchParams.get(\"path\");\n            } else {\n                state.startUrl = browser.location.pathname;\n            }\n        }\n\n        // The PWA can only be installed if the app is not already launched (display-mode standalone)\n        // For Apple devices, PWA are supported on any mobile version of Safari, or in desktop since version 17\n        // On Safari devices, the check is also done on the display-mode and we rely on the installationState to\n        // decide whether we must show the prompt or not\n        state.isSupportedOnBrowser =\n            browser.BeforeInstallPromptEvent !== undefined ||\n            (isBrowserSafari() &&\n                !isDisplayStandalone() &&\n                (isIOS() ||\n                    (isMacOS() && browser.navigator.userAgent.match(/Version\\/(\\d+)/)[1] >= 17)));\n\n        const installationState = _getInstallationState();\n\n        if (state.isSupportedOnBrowser) {\n            if (BEFOREINSTALLPROMPT_EVENT) {\n                _handleBeforeInstallPrompt(BEFOREINSTALLPROMPT_EVENT, installationState);\n                BEFOREINSTALLPROMPT_EVENT = null; // clear this variable as it is no longer useful\n            }\n            // If a user declines the prompt, the browser would triggered it once again. We must be able to catch it\n            REGISTER_BEFOREINSTALLPROMPT_EVENT = (ev) => {\n                _handleBeforeInstallPrompt(ev, installationState);\n            };\n            if (isBrowserSafari()) {\n                // since those platforms don't rely on the beforeinstallprompt event, we handle it ourselves\n                state.canPromptToInstall = installationState !== \"dismissed\";\n                state.isAvailable = true;\n            }\n        }\n\n        function _handleBeforeInstallPrompt(ev, installationState) {\n            nativePrompt = ev;\n            if (installationState === \"accepted\") {\n                // If this event is triggered with the installationState stored, it means that the app has been\n                // removed since its installation. The prompt can be displayed, and the installation state is reset.\n                if (!isDisplayStandalone()) {\n                    // In Scoped Apps, the event might be triggered if a manifest with a different scope is available\n                    _removeInstallationState();\n                }\n            }\n            state.canPromptToInstall = installationState !== \"dismissed\";\n            state.isAvailable = true;\n        }\n\n        async function getManifest() {\n            if (!_manifest) {\n                const manifest = await get(\n                    document.querySelector(\"link[rel=manifest\")?.getAttribute(\"href\"),\n                    \"text\"\n                );\n                _manifest = JSON.parse(manifest);\n            }\n            return _manifest;\n        }\n\n        // This function don't guarantee the scope is still currently installed on the device\n        // The only way to know that is by relying on the BeforeInstallPrompt event from the\n        // page linking the app manifest. This only serves to indicate that the app has previously\n        // been installed\n        function hasScopeBeenInstalled(scope) {\n            return _getInstallationState(scope) === \"accepted\";\n        }\n\n        async function show({ onDone } = {}) {\n            if (!state.isAvailable) {\n                return;\n            }\n            if (nativePrompt) {\n                const res = await nativePrompt.prompt();\n                _setInstallationState(res.outcome);\n                state.canPromptToInstall = false;\n                if (onDone) {\n                    onDone(res);\n                }\n            } else if (isBrowserSafari()) {\n                // since those platforms don't support a native installation prompt yet, we\n                // show a custom dialog to explain how to pin the app to the application menu\n                dialog.add(InstallPrompt, {\n                    onClose: () => {\n                        if (onDone) {\n                            onDone({});\n                        }\n                        this.decline();\n                    },\n                });\n            }\n        }\n\n        function decline() {\n            _setInstallationState(\"dismissed\");\n            state.canPromptToInstall = false;\n        }\n\n        return state;\n    },\n};\nserviceRegistry.add(\"pwa\", pwaService);\n", "import { evaluate } from \"./py_interpreter\";\nimport { parse } from \"./py_parser\";\nimport { tokenize } from \"./py_tokenizer\";\n\nexport { evaluate } from \"./py_interpreter\";\nexport { parse } from \"./py_parser\";\nexport { tokenize } from \"./py_tokenizer\";\nexport { formatAST } from \"./py_utils\";\n\n/**\n * @typedef { import(\"./py_tokenizer\").Token } Token\n * @typedef { import(\"./py_parser\").AST } AST\n */\n\n/**\n * Parses an expression into a valid AST representation\n\n * @param {string} expr\n * @returns { AST }\n */\nexport function parseExpr(expr) {\n    const tokens = tokenize(expr);\n    return parse(tokens);\n}\n\n/**\n * Evaluates a python expression\n *\n * @param {string} expr\n * @param {Object} [context]\n * @returns {any}\n */\nexport function evaluateExpr(expr, context = {}) {\n    let ast;\n    try {\n        ast = parseExpr(expr);\n    } catch (error) {\n        throw new EvalError(`Can not parse python expression: (${expr})\\nError: ${error.message}`);\n    }\n    try {\n        return evaluate(ast, context);\n    } catch (error) {\n        throw new EvalError(`Can not evaluate python expression: (${expr})\\nError: ${error.message}`);\n    }\n}\n\n/**\n * Evaluates a python expression to return a boolean.\n *\n * @param {string} expr\n * @param {Object} [context]\n * @returns {any}\n */\nexport function evaluateBooleanExpr(expr, context = {}) {\n    if (!expr || expr === 'False' || expr === '0') {\n        return false;\n    }\n    if (expr === 'True' || expr === '1') {\n        return true;\n    }\n    return evaluateExpr(`bool(${expr})`, context);\n}\n", "import { PyDate, PyDateTime, PyRelativeDelta, PyTime, PyTimeDelta } from \"./py_date\";\n\nexport class EvaluationError extends Error {}\n\n/**\n * @param {any} iterable\n * @param {Function} func\n */\nexport function execOnIterable(iterable, func) {\n    if (iterable === null) {\n        // new Set(null) is fine in js but set(None) (-> new Set(null))\n        // is not in Python\n        throw new EvaluationError(`value not iterable`);\n    }\n    if (typeof iterable === \"object\" && !Array.isArray(iterable) && !(iterable instanceof Set)) {\n        // dicts are considered as iterable in Python\n        iterable = Object.keys(iterable);\n    }\n    if (typeof iterable?.[Symbol.iterator] !== \"function\") {\n        // rules out undefined and other values not iterable\n        throw new EvaluationError(`value not iterable`);\n    }\n    return func(iterable);\n}\n\nexport const BUILTINS = {\n    /**\n     * @param {any} value\n     * @returns {boolean}\n     */\n    bool(value) {\n        switch (typeof value) {\n            case \"number\":\n                return value !== 0;\n            case \"string\":\n                return value !== \"\";\n            case \"boolean\":\n                return value;\n            case \"object\":\n                if (value === null || value === undefined) {\n                    return false;\n                }\n                if (value.isTrue) {\n                    return value.isTrue();\n                }\n                if (value instanceof Array) {\n                    return !!value.length;\n                }\n                if (value instanceof Set) {\n                    return !!value.size;\n                }\n                return Object.keys(value).length !== 0;\n        }\n        return true;\n    },\n\n    set(iterable) {\n        if (arguments.length > 2) {\n            // we always receive at least one argument: kwargs (return fnValue(...args, kwargs); in FunctionCall case)\n            throw new EvaluationError(\n                `set expected at most 1 argument, got (${arguments.length - 1}`\n            );\n        }\n        return execOnIterable(iterable, (iterable) => {\n            return new Set(iterable);\n        });\n    },\n\n    max(...args) {\n        // kwargs are not supported by Math.max.\n        return Math.max(...args.slice(0, -1));\n    },\n\n    min(...args) {\n        // kwargs are not supported by Math.min.\n        return Math.min(...args.slice(0, -1));\n    },\n\n    time: {\n        strftime(format) {\n            return PyDateTime.now().strftime(format);\n        },\n    },\n\n    context_today() {\n        return PyDate.today();\n    },\n\n    get current_date() {\n        // deprecated: today should be prefered\n        return this.today;\n    },\n\n    get today() {\n        return PyDate.today().strftime(\"%Y-%m-%d\");\n    },\n\n    get now() {\n        return PyDateTime.now().strftime(\"%Y-%m-%d %H:%M:%S\");\n    },\n\n    datetime: {\n        time: PyTime,\n        timedelta: PyTimeDelta,\n        datetime: PyDateTime,\n        date: PyDate,\n    },\n\n    relativedelta: PyRelativeDelta,\n\n    true: true,\n    false: false,\n};\n", "import { parseArgs } from \"./py_parser\";\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class AssertionError extends Error {}\nexport class ValueError extends Error {}\nexport class NotSupportedError extends Error {}\n\n// -----------------------------------------------------------------------------\n// helpers\n// -----------------------------------------------------------------------------\n\nfunction fmt2(n) {\n    return String(n).padStart(2, \"0\");\n}\nfunction fmt4(n) {\n    return String(n).padStart(4, \"0\");\n}\n\n/**\n * computes (Math.floor(a/b), a%b and passes that to the callback.\n *\n * returns the callback's result\n */\nfunction divmod(a, b, fn) {\n    let mod = a % b;\n    // in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a\n    // round of b\n    if ((mod > 0 && b < 0) || (mod < 0 && b > 0)) {\n        mod += b;\n    }\n    return fn(Math.floor(a / b), mod);\n}\n\nfunction assert(bool, message = \"AssertionError\") {\n    if (!bool) {\n        throw new AssertionError(message);\n    }\n}\n\nconst DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst DAYS_BEFORE_MONTH = [null];\n\nfor (let dbm = 0, i = 1; i < DAYS_IN_MONTH.length; ++i) {\n    DAYS_BEFORE_MONTH.push(dbm);\n    dbm += DAYS_IN_MONTH[i];\n}\n\nfunction daysInMonth(year, month) {\n    if (month === 2 && isLeap(year)) {\n        return 29;\n    }\n    return DAYS_IN_MONTH[month];\n}\n\nfunction isLeap(year) {\n    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n}\n\nfunction daysBeforeYear(year) {\n    const y = year - 1;\n    return y * 365 + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400);\n}\n\nfunction daysBeforeMonth(year, month) {\n    const postLeapFeb = month > 2 && isLeap(year);\n    return DAYS_BEFORE_MONTH[month] + (postLeapFeb ? 1 : 0);\n}\n\nfunction ymd2ord(year, month, day) {\n    const dim = daysInMonth(year, month);\n    if (!(1 <= day && day <= dim)) {\n        throw new ValueError(`day must be in 1..${dim}`);\n    }\n    return daysBeforeYear(year) + daysBeforeMonth(year, month) + day;\n}\n\nconst DI400Y = daysBeforeYear(401);\nconst DI100Y = daysBeforeYear(101);\nconst DI4Y = daysBeforeYear(5);\n\nfunction ord2ymd(n) {\n    --n;\n    let n400, n100, n4, n1, n0;\n    divmod(n, DI400Y, function (_n400, n) {\n        n400 = _n400;\n        divmod(n, DI100Y, function (_n100, n) {\n            n100 = _n100;\n            divmod(n, DI4Y, function (_n4, n) {\n                n4 = _n4;\n                divmod(n, 365, function (_n1, n) {\n                    n1 = _n1;\n                    n0 = n;\n                });\n            });\n        });\n    });\n\n    n = n0;\n    const year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1;\n    if (n1 == 4 || n100 == 100) {\n        assert(n0 === 0);\n        return {\n            year: year - 1,\n            month: 12,\n            day: 31,\n        };\n    }\n\n    const leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);\n    assert(leapyear == isLeap(year));\n    let month = (n + 50) >> 5;\n    let preceding = DAYS_BEFORE_MONTH[month] + (month > 2 && leapyear ? 1 : 0);\n    if (preceding > n) {\n        --month;\n        preceding -= DAYS_IN_MONTH[month] + (month === 2 && leapyear ? 1 : 0);\n    }\n    n -= preceding;\n    return {\n        year: year,\n        month: month,\n        day: n + 1,\n    };\n}\n\n/**\n * Converts the stuff passed in into a valid date, applying overflows as needed\n */\nfunction tmxxx(year, month, day, hour, minute, second, microsecond) {\n    hour = hour || 0;\n    minute = minute || 0;\n    second = second || 0;\n    microsecond = microsecond || 0;\n\n    if (microsecond < 0 || microsecond > 999999) {\n        divmod(microsecond, 1000000, function (carry, ms) {\n            microsecond = ms;\n            second += carry;\n        });\n    }\n    if (second < 0 || second > 59) {\n        divmod(second, 60, function (carry, s) {\n            second = s;\n            minute += carry;\n        });\n    }\n    if (minute < 0 || minute > 59) {\n        divmod(minute, 60, function (carry, m) {\n            minute = m;\n            hour += carry;\n        });\n    }\n    if (hour < 0 || hour > 23) {\n        divmod(hour, 24, function (carry, h) {\n            hour = h;\n            day += carry;\n        });\n    }\n    // That was easy.  Now it gets muddy:  the proper range for day\n    // can't be determined without knowing the correct month and year,\n    // but if day is, e.g., plus or minus a million, the current month\n    // and year values make no sense (and may also be out of bounds\n    // themselves).\n    // Saying 12 months == 1 year should be non-controversial.\n    if (month < 1 || month > 12) {\n        divmod(month - 1, 12, function (carry, m) {\n            month = m + 1;\n            year += carry;\n        });\n    }\n    // Now only day can be out of bounds (year may also be out of bounds\n    // for a datetime object, but we don't care about that here).\n    // If day is out of bounds, what to do is arguable, but at least the\n    // method here is principled and explainable.\n    const dim = daysInMonth(year, month);\n    if (day < 1 || day > dim) {\n        // Move day-1 days from the first of the month.  First try to\n        // get off cheap if we're only one day out of range (adjustments\n        // for timezone alone can't be worse than that).\n        if (day === 0) {\n            --month;\n            if (month > 0) {\n                day = daysInMonth(year, month);\n            } else {\n                --year;\n                month = 12;\n                day = 31;\n            }\n        } else if (day == dim + 1) {\n            ++month;\n            day = 1;\n            if (month > 12) {\n                month = 1;\n                ++year;\n            }\n        } else {\n            const r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));\n            year = r.year;\n            month = r.month;\n            day = r.day;\n        }\n    }\n    return {\n        year: year,\n        month: month,\n        day: day,\n        hour: hour,\n        minute: minute,\n        second: second,\n        microsecond: microsecond,\n    };\n}\n\n// -----------------------------------------------------------------------------\n// Date/Time and related classes\n// -----------------------------------------------------------------------------\n\nexport class PyDate {\n    /**\n     * @returns {PyDate}\n     */\n    static today() {\n        return this.convertDate(new Date());\n    }\n\n    /**\n     * Convert a date object into PyDate\n     * @param {Date} date\n     * @returns {PyDate}\n     */\n    static convertDate(date) {\n        const year = date.getFullYear();\n        const month = date.getMonth() + 1;\n        const day = date.getDate();\n        return new PyDate(year, month, day);\n    }\n\n    /**\n     * @param {integer} year\n     * @param {integer} month\n     * @param {integer} day\n     */\n    constructor(year, month, day) {\n        this.year = year;\n        this.month = month; // 1-indexed => 1 = january, 2 = february, ...\n        this.day = day; // 1-indexed => 1 = first day of month, ...\n    }\n\n    /**\n     * @param  {...any} args\n     * @returns {PyDate}\n     */\n    static create(...args) {\n        const { year, month, day } = parseArgs(args, [\"year\", \"month\", \"day\"]);\n        return new PyDate(year, month, day);\n    }\n\n    /**\n     * @param {PyTimeDelta} timedelta\n     * @returns {PyDate}\n     */\n    add(timedelta) {\n        const s = tmxxx(this.year, this.month, this.day + timedelta.days);\n        return new PyDate(s.year, s.month, s.day);\n    }\n\n    /**\n     * @param {any} other\n     * @returns {boolean}\n     */\n    isEqual(other) {\n        if (!(other instanceof PyDate)) {\n            return false;\n        }\n        return this.year === other.year && this.month === other.month && this.day === other.day;\n    }\n\n    /**\n     * @param {string} format\n     * @returns {string}\n     */\n    strftime(format) {\n        return format.replace(/%([A-Za-z])/g, (m, c) => {\n            switch (c) {\n                case \"Y\":\n                    return fmt4(this.year);\n                case \"m\":\n                    return fmt2(this.month);\n                case \"d\":\n                    return fmt2(this.day);\n            }\n            throw new ValueError(`No known conversion for ${m}`);\n        });\n    }\n\n    /**\n     * @param {PyTimeDelta | PyDate} other\n     * @returns {PyDate | PyTimeDelta}\n     */\n    substract(other) {\n        if (other instanceof PyTimeDelta) {\n            return this.add(other.negate());\n        }\n        if (other instanceof PyDate) {\n            return PyTimeDelta.create(this.toordinal() - other.toordinal());\n        }\n        throw new NotSupportedError();\n    }\n\n    /**\n     * @returns {string}\n     */\n    toJSON() {\n        return this.strftime(\"%Y-%m-%d\");\n    }\n\n    /**\n     * @returns {integer}\n     */\n    toordinal() {\n        return ymd2ord(this.year, this.month, this.day);\n    }\n}\n\nexport class PyDateTime {\n    /**\n     * @returns {PyDateTime}\n     */\n    static now() {\n        return this.convertDate(new Date());\n    }\n\n    /**\n     * Convert a date object into PyDateTime\n     * @param {Date} date\n     * @returns {PyDateTime}\n     */\n    static convertDate(date) {\n        const year = date.getFullYear();\n        const month = date.getMonth() + 1;\n        const day = date.getDate();\n        const hour = date.getHours();\n        const minute = date.getMinutes();\n        const second = date.getSeconds();\n        return new PyDateTime(year, month, day, hour, minute, second, 0);\n    }\n\n    /**\n     * @param  {...any} args\n     * @returns {PyDateTime}\n     */\n    static create(...args) {\n        const namedArgs = parseArgs(args, [\n            \"year\",\n            \"month\",\n            \"day\",\n            \"hour\",\n            \"minute\",\n            \"second\",\n            \"microsecond\",\n        ]);\n        const year = namedArgs.year;\n        const month = namedArgs.month;\n        const day = namedArgs.day;\n        const hour = namedArgs.hour || 0;\n        const minute = namedArgs.minute || 0;\n        const second = namedArgs.second || 0;\n        const ms = namedArgs.micro / 1000 || 0;\n        return new PyDateTime(year, month, day, hour, minute, second, ms);\n    }\n\n    /**\n     * @param  {...any} args\n     * @returns {PyDateTime}\n     */\n    static combine(...args) {\n        const { date, time } = parseArgs(args, [\"date\", \"time\"]);\n        // not sure. should we go through constructor instead? what about args normalization?\n        return PyDateTime.create(\n            date.year,\n            date.month,\n            date.day,\n            time.hour,\n            time.minute,\n            time.second\n        );\n    }\n\n    /**\n     * @param {integer} year\n     * @param {integer} month\n     * @param {integer} day\n     * @param {integer} hour\n     * @param {integer} minute\n     * @param {integer} second\n     * @param {integer} microsecond\n     */\n    constructor(year, month, day, hour, minute, second, microsecond) {\n        this.year = year;\n        this.month = month; // 1-indexed => 1 = january, 2 = february, ...\n        this.day = day; // 1-indexed => 1 = first day of month, ...\n        this.hour = hour;\n        this.minute = minute;\n        this.second = second;\n        this.microsecond = microsecond;\n    }\n\n    /**\n     * @param {PyTimeDelta} timedelta\n     * @returns {PyDate}\n     */\n    add(timedelta) {\n        const s = tmxxx(\n            this.year,\n            this.month,\n            this.day + timedelta.days,\n            this.hour,\n            this.minute,\n            this.second + timedelta.seconds,\n            this.microsecond + timedelta.microseconds\n        );\n        // does not seem to closely follow python implementation.\n        return new PyDateTime(s.year, s.month, s.day, s.hour, s.minute, s.second, s.microsecond);\n    }\n\n    /**\n     * @param {any} other\n     * @returns {boolean}\n     */\n    isEqual(other) {\n        if (!(other instanceof PyDateTime)) {\n            return false;\n        }\n        return (\n            this.year === other.year &&\n            this.month === other.month &&\n            this.day === other.day &&\n            this.hour === other.hour &&\n            this.minute === other.minute &&\n            this.second === other.second &&\n            this.microsecond === other.microsecond\n        );\n    }\n\n    /**\n     * @param {string} format\n     * @returns {string}\n     */\n    strftime(format) {\n        return format.replace(/%([A-Za-z])/g, (m, c) => {\n            switch (c) {\n                case \"Y\":\n                    return fmt4(this.year);\n                case \"m\":\n                    return fmt2(this.month);\n                case \"d\":\n                    return fmt2(this.day);\n                case \"H\":\n                    return fmt2(this.hour);\n                case \"M\":\n                    return fmt2(this.minute);\n                case \"S\":\n                    return fmt2(this.second);\n            }\n            throw new ValueError(`No known conversion for ${m}`);\n        });\n    }\n\n    /**\n     * @param {PyTimeDelta} timedelta\n     * @returns {PyDateTime}\n     */\n    substract(timedelta) {\n        return this.add(timedelta.negate());\n    }\n\n    /**\n     * @returns {string}\n     */\n    toJSON() {\n        return this.strftime(\"%Y-%m-%d %H:%M:%S\");\n    }\n\n    /**\n     * @returns {PyDateTime}\n     */\n    to_utc() {\n        const d = new Date(\n            this.year,\n            this.month - 1,\n            this.day,\n            this.hour,\n            this.minute,\n            this.second\n        );\n        const timedelta = PyTimeDelta.create({ minutes: d.getTimezoneOffset() });\n        return this.add(timedelta);\n    }\n}\n\nexport class PyTime extends PyDate {\n    /**\n     * @param  {...any} args\n     * @returns {PyTime}\n     */\n    static create(...args) {\n        const namedArgs = parseArgs(args, [\"hour\", \"minute\", \"second\"]);\n        const hour = namedArgs.hour || 0;\n        const minute = namedArgs.minute || 0;\n        const second = namedArgs.second || 0;\n        return new PyTime(hour, minute, second);\n    }\n\n    constructor(hour, minute, second) {\n        const now = new Date();\n        const year = now.getFullYear();\n        const month = now.getMonth();\n        const day = now.getDate();\n        super(year, month, day);\n        this.hour = hour;\n        this.minute = minute;\n        this.second = second;\n    }\n\n    /**\n     * @param {string} format\n     * @returns {string}\n     */\n    strftime(format) {\n        return format.replace(/%([A-Za-z])/g, (m, c) => {\n            switch (c) {\n                case \"Y\":\n                    return fmt4(this.year);\n                case \"m\":\n                    return fmt2(this.month + 1);\n                case \"d\":\n                    return fmt2(this.day);\n                case \"H\":\n                    return fmt2(this.hour);\n                case \"M\":\n                    return fmt2(this.minute);\n                case \"S\":\n                    return fmt2(this.second);\n            }\n            throw new ValueError(`No known conversion for ${m}`);\n        });\n    }\n\n    toJSON() {\n        return this.strftime(\"%H:%M:%S\");\n    }\n}\n\n/*\n * This list is intended to be of that shape (32 days in december), it is used by\n * the algorithm that computes \"relativedelta yearday\". The algorithm was adapted\n * from the one in python (https://github.com/dateutil/dateutil/blob/2.7.3/dateutil/relativedelta.py#L199)\n */\nconst DAYS_IN_YEAR = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366];\n\nconst TIME_PERIODS = [\"hour\", \"minute\", \"second\"];\nconst PERIODS = [\"year\", \"month\", \"day\", ...TIME_PERIODS];\n\nconst RELATIVE_KEYS = \"years months weeks days hours minutes seconds microseconds leapdays\".split(\n    \" \"\n);\nconst ABSOLUTE_KEYS =\n    \"year month day hour minute second microsecond weekday nlyearday yearday\".split(\" \");\n\nconst argsSpec = [\"dt1\", \"dt2\"]; // all other arguments are kwargs\nexport class PyRelativeDelta {\n    /**\n     * @param  {...any} args\n     * @returns {PyRelativeDelta}\n     */\n    static create(...args) {\n        const params = parseArgs(args, argsSpec);\n        if (\"dt1\" in params) {\n            throw new Error(\"relativedelta(dt1, dt2) is not supported for now\");\n        }\n        for (const period of PERIODS) {\n            if (period in params) {\n                const val = params[period];\n                assert(val >= 0, `${period} ${val} is out of range`);\n            }\n        }\n\n        for (const key of RELATIVE_KEYS) {\n            params[key] = params[key] || 0;\n        }\n        for (const key of ABSOLUTE_KEYS) {\n            params[key] = key in params ? params[key] : null;\n        }\n        params.days += 7 * params.weeks;\n\n        let yearDay = 0;\n        if (params.nlyearday) {\n            yearDay = params.nlyearday;\n        } else if (params.yearday) {\n            yearDay = params.yearday;\n            if (yearDay > 59) {\n                params.leapDays = -1;\n            }\n        }\n\n        if (yearDay) {\n            for (let monthIndex = 0; monthIndex < DAYS_IN_YEAR.length; monthIndex++) {\n                if (yearDay <= DAYS_IN_YEAR[monthIndex]) {\n                    params.month = monthIndex + 1;\n                    if (monthIndex === 0) {\n                        params.day = yearDay;\n                    } else {\n                        params.day = yearDay - DAYS_IN_YEAR[monthIndex - 1];\n                    }\n                    break;\n                }\n            }\n        }\n\n        return new PyRelativeDelta(params);\n    }\n\n    /**\n     * @param {PyDateTime|PyDate} date\n     * @param {PyRelativeDelta} delta\n     * @returns {PyDateTime|PyDate}\n     */\n    static add(date, delta) {\n        if (!(date instanceof PyDate || date instanceof PyDateTime)) {\n            throw new NotSupportedError();\n        }\n\n        // First pass: we want to determine which is our target year and if we will apply leap days\n        const s = tmxxx(\n            (delta.year || date.year) + delta.years,\n            (delta.month || date.month) + delta.months,\n            delta.day || date.day,\n            delta.hour || date.hour || 0,\n            delta.minute || date.minute || 0,\n            delta.second || date.seconds || 0,\n            delta.microseconds || date.microseconds || 0\n        );\n\n        const newDateTime = new PyDateTime(\n            s.year,\n            s.month,\n            s.day,\n            s.hour,\n            s.minute,\n            s.second,\n            s.microsecond\n        );\n\n        let leapDays = 0;\n        if (delta.leapDays && newDateTime.month > 2 && isLeap(newDateTime.year)) {\n            leapDays = delta.leapDays;\n        }\n\n        // Second pass: apply the difference in days, and the difference in time values\n        const temp = newDateTime.add(\n            PyTimeDelta.create({\n                days: delta.days + leapDays,\n                hours: delta.hours,\n                minutes: delta.minutes,\n                seconds: delta.seconds,\n                microseconds: delta.microseconds,\n            })\n        );\n\n        // Determine the right return type:\n        // First we look at the type of the incoming date object,\n        // then we look at the actual time values held by the computed date.\n        const hasTime = Boolean(temp.hour || temp.minute || temp.second || temp.microsecond);\n        const returnDate =\n            !hasTime && date instanceof PyDate ? new PyDate(temp.year, temp.month, temp.day) : temp;\n\n        // Final pass: target the wanted day of the week (if necessary)\n        if (delta.weekday !== null) {\n            const wantedDow = delta.weekday + 1; // python: Monday is 0 ; JS: Monday is 1;\n            const _date = new Date(returnDate.year, returnDate.month - 1, returnDate.day);\n            const days = (7 - _date.getDay() + wantedDow) % 7;\n            return returnDate.add(new PyTimeDelta(days, 0, 0));\n        }\n        return returnDate;\n    }\n\n    /**\n     * @param {PyDateTime|PyDate} date\n     * @param {PyRelativeDelta} delta\n     * @returns {PyDateTime|PyDate}\n     */\n    static substract(date, delta) {\n        return PyRelativeDelta.add(date, delta.negate());\n    }\n\n    /**\n     * @param {Object} params\n     * @param {+1|-1} sign\n     */\n    constructor(params = {}, sign = +1) {\n        this.years = sign * params.years;\n        this.months = sign * params.months;\n        this.days = sign * params.days;\n        this.hours = sign * params.hours;\n        this.minutes = sign * params.minutes;\n        this.seconds = sign * params.seconds;\n        this.microseconds = sign * params.microseconds;\n\n        this.leapDays = params.leapDays;\n\n        this.year = params.year;\n        this.month = params.month;\n        this.day = params.day;\n        this.hour = params.hour;\n        this.minute = params.minute;\n        this.second = params.second;\n        this.microsecond = params.microsecond;\n\n        this.weekday = params.weekday;\n    }\n\n    /**\n     * @returns {PyRelativeDelta}\n     */\n    negate() {\n        return new PyRelativeDelta(this, -1);\n    }\n\n    isEqual(other) {\n        // For now we don't do normalization in the constructor (or create method).\n        // That is, we only compute the overflows at the time we add or substract.\n        // This is why we can't support isEqual for now.\n        throw new NotSupportedError();\n    }\n}\n\nconst TIME_DELTA_KEYS = \"weeks days hours minutes seconds milliseconds microseconds\".split(\" \");\n\n/**\n * Returns a \"pair\" with the fractional and integer parts of x\n * @param {float}\n * @returns {[float,integer]}\n */\nfunction modf(x) {\n    const mod = x % 1;\n    return [mod < 0 ? mod + 1 : mod, Math.floor(x)];\n}\n\nexport class PyTimeDelta {\n    /**\n     * @param  {...any} args\n     * @returns {PyTimeDelta}\n     */\n    static create(...args) {\n        const namedArgs = parseArgs(args, [\"days\", \"seconds\", \"microseconds\"]);\n        for (const key of TIME_DELTA_KEYS) {\n            namedArgs[key] = namedArgs[key] || 0;\n        }\n\n        // a timedelta can be created using TIME_DELTA_KEYS with float/integer values\n        // but only days, seconds, microseconds are kept internally.\n        // --> some normalization occurs here\n\n        let d = 0;\n        let s = 0;\n        let us = 0; // ~ \u03bcs standard notation for microseconds\n\n        const days = namedArgs.days + namedArgs.weeks * 7;\n        let seconds = namedArgs.seconds + 60 * namedArgs.minutes + 3600 * namedArgs.hours;\n        let microseconds = namedArgs.microseconds + 1000 * namedArgs.milliseconds;\n\n        const [dFrac, dInt] = modf(days);\n        d = dInt;\n        let daysecondsfrac = 0;\n        if (dFrac) {\n            const [dsFrac, dsInt] = modf(dFrac * 24 * 3600);\n            s = dsInt;\n            daysecondsfrac = dsFrac;\n        }\n\n        const [sFrac, sInt] = modf(seconds);\n        seconds = sInt;\n        const secondsfrac = sFrac + daysecondsfrac;\n\n        divmod(seconds, 24 * 3600, (days, seconds) => {\n            d += days;\n            s += seconds;\n        });\n\n        microseconds += secondsfrac * 1e6;\n        divmod(microseconds, 1000000, (seconds, microseconds) => {\n            divmod(seconds, 24 * 3600, (days, seconds) => {\n                d += days;\n                s += seconds;\n                us += Math.round(microseconds);\n            });\n        });\n\n        return new PyTimeDelta(d, s, us);\n    }\n\n    /**\n     * @param {integer} days\n     * @param {integer} seconds\n     * @param {integer} microseconds\n     */\n    constructor(days, seconds, microseconds) {\n        this.days = days;\n        this.seconds = seconds;\n        this.microseconds = microseconds;\n    }\n\n    /**\n     * @param {PyTimeDelta} other\n     * @returns {PyTimeDelta}\n     */\n    add(other) {\n        return PyTimeDelta.create({\n            days: this.days + other.days,\n            seconds: this.seconds + other.seconds,\n            microseconds: this.microseconds + other.microseconds,\n        });\n    }\n\n    /**\n     * @param {integer} n\n     * @returns {PyTimeDelta}\n     */\n    divide(n) {\n        const us = (this.days * 24 * 3600 + this.seconds) * 1e6 + this.microseconds;\n        return PyTimeDelta.create({ microseconds: Math.floor(us / n) });\n    }\n\n    /**\n     * @param {any} other\n     * @returns {boolean}\n     */\n    isEqual(other) {\n        if (!(other instanceof PyTimeDelta)) {\n            return false;\n        }\n        return (\n            this.days === other.days &&\n            this.seconds === other.seconds &&\n            this.microseconds === other.microseconds\n        );\n    }\n\n    /**\n     * @returns {boolean}\n     */\n    isTrue() {\n        return this.days !== 0 || this.seconds !== 0 || this.microseconds !== 0;\n    }\n\n    /**\n     * @param {float} n\n     * @returns {PyTimeDelta}\n     */\n    multiply(n) {\n        return PyTimeDelta.create({\n            days: n * this.days,\n            seconds: n * this.seconds,\n            microseconds: n * this.microseconds,\n        });\n    }\n\n    /**\n     * @returns {PyTimeDelta}\n     */\n    negate() {\n        return PyTimeDelta.create({\n            days: -this.days,\n            seconds: -this.seconds,\n            microseconds: -this.microseconds,\n        });\n    }\n\n    /**\n     * @param {PyTimeDelta} other\n     * @returns {PyTimeDelta}\n     */\n    substract(other) {\n        return PyTimeDelta.create({\n            days: this.days - other.days,\n            seconds: this.seconds - other.seconds,\n            microseconds: this.microseconds - other.microseconds,\n        });\n    }\n\n    /**\n     * @returns {float}\n     */\n    total_seconds() {\n        return this.days * 86400 + this.seconds + this.microseconds / 1000000;\n    }\n}\n", "import { BUILTINS, EvaluationError, execOnIterable } from \"./py_builtin\";\nimport {\n    NotSupportedError,\n    PyDate,\n    PyDateTime,\n    PyRelativeDelta,\n    PyTime,\n    PyTimeDelta,\n} from \"./py_date\";\nimport { PY_DICT, toPyDict } from \"./py_utils\";\nimport { parseArgs } from \"./py_parser\";\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef { import(\"./py_parser\").AST } AST\n */\n\n// -----------------------------------------------------------------------------\n// Constants and helpers\n// -----------------------------------------------------------------------------\n\nconst isTrue = BUILTINS.bool;\n\n/**\n * @param {AST} ast\n * @param {Object} context\n * @returns {any}\n */\nfunction applyUnaryOp(ast, context) {\n    const value = evaluate(ast.right, context);\n    switch (ast.op) {\n        case \"-\":\n            if (value instanceof Object && value.negate) {\n                return value.negate();\n            }\n            return -value;\n        case \"+\":\n            return value;\n        case \"not\":\n            return !isTrue(value);\n    }\n    throw new EvaluationError(`Unknown unary operator: ${ast.op}`);\n}\n\n/**\n * We want to maintain this order:\n *   None < number (boolean) < dict < string < list < dict\n * So, each type is mapped to a number to represent that order\n *\n * @param {any} val\n * @returns {number} index type\n */\nfunction pytypeIndex(val) {\n    switch (typeof val) {\n        case \"object\":\n            // None, List, Object, Dict\n            return val === null ? 1 : Array.isArray(val) ? 5 : 3;\n        case \"number\":\n            return 2;\n        case \"string\":\n            return 4;\n    }\n    throw new EvaluationError(`Unknown type: ${typeof val}`);\n}\n\n/**\n * @param {Object} obj\n * @returns {boolean}\n */\nfunction isConstructor(obj) {\n    return !!obj.prototype && !!obj.prototype.constructor.name;\n}\n\n/**\n * Compare two values\n *\n * @param {any} left\n * @param {any} right\n * @returns {boolean}\n */\nfunction isLess(left, right) {\n    if (typeof left === \"number\" && typeof right === \"number\") {\n        return left < right;\n    }\n    if (typeof left === \"boolean\") {\n        left = left ? 1 : 0;\n    }\n    if (typeof right === \"boolean\") {\n        right = right ? 1 : 0;\n    }\n    const leftIndex = pytypeIndex(left);\n    const rightIndex = pytypeIndex(right);\n    if (leftIndex === rightIndex) {\n        return left < right;\n    }\n    return leftIndex < rightIndex;\n}\n\n/**\n * @param {any} left\n * @param {any} right\n * @returns {boolean}\n */\nfunction isEqual(left, right) {\n    if (typeof left !== typeof right) {\n        if (typeof left === \"boolean\" && typeof right === \"number\") {\n            return right === (left ? 1 : 0);\n        }\n        if (typeof left === \"number\" && typeof right === \"boolean\") {\n            return left === (right ? 1 : 0);\n        }\n        return false;\n    }\n    if (left instanceof Object && left.isEqual) {\n        return left.isEqual(right);\n    }\n    return left === right;\n}\n\n/**\n * @param {any} left\n * @param {any} right\n * @returns {boolean}\n */\nfunction isIn(left, right) {\n    if (Array.isArray(right)) {\n        return right.includes(left);\n    }\n    if (typeof right === \"string\" && typeof left === \"string\") {\n        return right.includes(left);\n    }\n    if (typeof right === \"object\") {\n        return left in right;\n    }\n    return false;\n}\n\n/**\n * @param {AST} ast\n * @param {object} context\n * @returns {any}\n */\nfunction applyBinaryOp(ast, context) {\n    const left = evaluate(ast.left, context);\n    const right = evaluate(ast.right, context);\n    switch (ast.op) {\n        case \"+\": {\n            const relativeDeltaOnLeft = left instanceof PyRelativeDelta;\n            const relativeDeltaOnRight = right instanceof PyRelativeDelta;\n            if (relativeDeltaOnLeft || relativeDeltaOnRight) {\n                const date = relativeDeltaOnLeft ? right : left;\n                const delta = relativeDeltaOnLeft ? left : right;\n                return PyRelativeDelta.add(date, delta);\n            }\n\n            const timeDeltaOnLeft = left instanceof PyTimeDelta;\n            const timeDeltaOnRight = right instanceof PyTimeDelta;\n            if (timeDeltaOnLeft && timeDeltaOnRight) {\n                return left.add(right);\n            }\n            if (timeDeltaOnLeft) {\n                if (right instanceof PyDate || right instanceof PyDateTime) {\n                    return right.add(left);\n                } else {\n                    throw new NotSupportedError();\n                }\n            }\n            if (timeDeltaOnRight) {\n                if (left instanceof PyDate || left instanceof PyDateTime) {\n                    return left.add(right);\n                } else {\n                    throw new NotSupportedError();\n                }\n            }\n            if (left instanceof Array && right instanceof Array) {\n                return [...left, ...right];\n            }\n\n            return left + right;\n        }\n        case \"-\": {\n            const isRightDelta = right instanceof PyRelativeDelta;\n            if (isRightDelta) {\n                return PyRelativeDelta.substract(left, right);\n            }\n\n            const timeDeltaOnRight = right instanceof PyTimeDelta;\n            if (timeDeltaOnRight) {\n                if (left instanceof PyTimeDelta) {\n                    return left.substract(right);\n                } else if (left instanceof PyDate || left instanceof PyDateTime) {\n                    return left.substract(right);\n                } else {\n                    throw new NotSupportedError();\n                }\n            }\n\n            if (left instanceof PyDate) {\n                return left.substract(right);\n            }\n            return left - right;\n        }\n        case \"*\": {\n            const timeDeltaOnLeft = left instanceof PyTimeDelta;\n            const timeDeltaOnRight = right instanceof PyTimeDelta;\n            if (timeDeltaOnLeft || timeDeltaOnRight) {\n                const number = timeDeltaOnLeft ? right : left;\n                const delta = timeDeltaOnLeft ? left : right;\n                return delta.multiply(number); // check number type?\n            }\n\n            return left * right;\n        }\n        case \"/\":\n            return left / right;\n        case \"%\":\n            return left % right;\n        case \"//\":\n            if (left instanceof PyTimeDelta) {\n                return left.divide(right); // check number type?\n            }\n            return Math.floor(left / right);\n        case \"**\":\n            return left ** right;\n        case \"==\":\n            return isEqual(left, right);\n        case \"<>\":\n        case \"!=\":\n            return !isEqual(left, right);\n        case \"<\":\n            return isLess(left, right);\n        case \">\":\n            return isLess(right, left);\n        case \">=\":\n            return isEqual(left, right) || isLess(right, left);\n        case \"<=\":\n            return isEqual(left, right) || isLess(left, right);\n        case \"in\":\n            return isIn(left, right);\n        case \"not in\":\n            return !isIn(left, right);\n    }\n    throw new EvaluationError(`Unknown binary operator: ${ast.op}`);\n}\n\nconst DICT = {\n    get(...args) {\n        const { key, defValue } = parseArgs(args, [\"key\", \"defValue\"]);\n        if (key in this) {\n            return this[key];\n        } else if (defValue) {\n            return defValue;\n        }\n        return null;\n    },\n};\n\nconst STRING = {\n    lower() {\n        return this.toLowerCase();\n    },\n    upper() {\n        return this.toUpperCase();\n    },\n};\n\nfunction applyFunc(key, func, set, ...args) {\n    // we always receive at least one argument: kwargs (return fnValue(...args, kwargs); in FunctionCall case)\n    if (args.length === 1) {\n        return new Set(set);\n    }\n    if (args.length > 2) {\n        throw new EvaluationError(\n            `${key}: py_js supports at most 1 argument, got (${args.length - 1})`\n        );\n    }\n    return execOnIterable(args[0], func);\n}\n\nconst SET = {\n    intersection(...args) {\n        return applyFunc(\n            \"intersection\",\n            (iterable) => {\n                const intersection = new Set();\n                for (const i of iterable) {\n                    if (this.has(i)) {\n                        intersection.add(i);\n                    }\n                }\n                return intersection;\n            },\n            this,\n            ...args\n        );\n    },\n    difference(...args) {\n        return applyFunc(\n            \"difference\",\n            (iterable) => {\n                iterable = new Set(iterable);\n                const difference = new Set();\n                for (const e of this) {\n                    if (!iterable.has(e)) {\n                        difference.add(e);\n                    }\n                }\n                return difference;\n            },\n            this,\n            ...args\n        );\n    },\n    union(...args) {\n        return applyFunc(\"union\", (iterable) => new Set([...this, ...iterable]), this, ...args);\n    },\n};\n\n// -----------------------------------------------------------------------------\n// Evaluate function\n// -----------------------------------------------------------------------------\n\n/**\n * @param {Function} _class the class whose methods we want\n * @returns {Function[]} an array containing the methods defined on the class,\n *  including the constructor\n */\nfunction methods(_class) {\n    return Object.getOwnPropertyNames(_class.prototype).map((prop) => _class.prototype[prop]);\n}\n\nconst allowedFns = new Set([\n    BUILTINS.time.strftime,\n    BUILTINS.set,\n    BUILTINS.bool,\n    BUILTINS.min,\n    BUILTINS.max,\n    BUILTINS.context_today,\n    BUILTINS.datetime.datetime.now,\n    BUILTINS.datetime.datetime.combine,\n    BUILTINS.datetime.date.today,\n    ...methods(BUILTINS.relativedelta),\n    ...Object.values(BUILTINS.datetime).flatMap((obj) => methods(obj)),\n    ...Object.values(SET),\n    ...Object.values(DICT),\n    ...Object.values(STRING),\n]);\n\nconst unboundFn = Symbol(\"unbound function\");\n\n/**\n * @param {AST} ast\n * @param {Object} context\n * @returns {any}\n */\nexport function evaluate(ast, context = {}) {\n    const dicts = new Set();\n    let pyContext;\n    const evalContext = Object.create(context);\n    if (!evalContext.context) {\n        Object.defineProperty(evalContext, \"context\", {\n            get() {\n                if (!pyContext) {\n                    pyContext = toPyDict(context);\n                }\n                return pyContext;\n            },\n        });\n    }\n\n    function _innerEvaluate(ast) {\n        switch (ast.type) {\n            case 0 /* Number */:\n            case 1 /* String */:\n                return ast.value;\n            case 5 /* Name */:\n                if (ast.value in evalContext) {\n                    return evalContext[ast.value];\n                } else if (ast.value in BUILTINS) {\n                    return BUILTINS[ast.value];\n                } else {\n                    throw new EvaluationError(`Name '${ast.value}' is not defined`);\n                }\n            case 3 /* None */:\n                return null;\n            case 2 /* Boolean */:\n                return ast.value;\n            case 6 /* UnaryOperator */:\n                return applyUnaryOp(ast, evalContext);\n            case 7 /* BinaryOperator */:\n                return applyBinaryOp(ast, evalContext);\n            case 14 /* BooleanOperator */: {\n                const left = _evaluate(ast.left);\n                if (ast.op === \"and\") {\n                    return isTrue(left) ? _evaluate(ast.right) : left;\n                } else {\n                    return isTrue(left) ? left : _evaluate(ast.right);\n                }\n            }\n            case 4 /* List */:\n            case 10 /* Tuple */:\n                return ast.value.map(_evaluate);\n            case 11 /* Dictionary */: {\n                const dict = {};\n                for (const key in ast.value) {\n                    dict[key] = _evaluate(ast.value[key]);\n                }\n                dicts.add(dict);\n                return dict;\n            }\n            case 8 /* FunctionCall */: {\n                const fnValue = _evaluate(ast.fn);\n                const args = ast.args.map(_evaluate);\n                const kwargs = {};\n                for (const kwarg in ast.kwargs) {\n                    kwargs[kwarg] = _evaluate(ast.kwargs[kwarg]);\n                }\n                if (\n                    fnValue === PyDate ||\n                    fnValue === PyDateTime ||\n                    fnValue === PyTime ||\n                    fnValue === PyRelativeDelta ||\n                    fnValue === PyTimeDelta\n                ) {\n                    return fnValue.create(...args, kwargs);\n                }\n                return fnValue(...args, kwargs);\n            }\n            case 12 /* Lookup */: {\n                const dict = _evaluate(ast.target);\n                const key = _evaluate(ast.key);\n                return dict[key];\n            }\n            case 13 /* If */: {\n                if (isTrue(_evaluate(ast.condition))) {\n                    return _evaluate(ast.ifTrue);\n                } else {\n                    return _evaluate(ast.ifFalse);\n                }\n            }\n            case 15 /* ObjLookup */: {\n                let left = _evaluate(ast.obj);\n                let result;\n                if (dicts.has(left) || Object.isPrototypeOf.call(PY_DICT, left)) {\n                    // this is a dictionary => need to apply dict methods\n                    result = DICT[ast.key];\n                } else if (typeof left === \"string\") {\n                    result = STRING[ast.key];\n                } else if (left instanceof Set) {\n                    result = SET[ast.key];\n                } else if (ast.key == \"get\" && typeof left === \"object\") {\n                    result = DICT[ast.key];\n                    left = toPyDict(left);\n                } else {\n                    result = left[ast.key];\n                }\n                if (typeof result === \"function\") {\n                    if (!isConstructor(result)) {\n                        const bound = result.bind(left);\n                        bound[unboundFn] = result;\n                        return bound;\n                    }\n                }\n                return result;\n            }\n        }\n        throw new EvaluationError(`AST of type ${ast.type} cannot be evaluated`);\n    }\n\n    /**\n     * @param {AST} ast\n     */\n    function _evaluate(ast) {\n        const val = _innerEvaluate(ast);\n        if (typeof val === \"function\" && !allowedFns.has(val) && !allowedFns.has(val[unboundFn])) {\n            throw new Error(\"Invalid Function Call\");\n        }\n        return val;\n    }\n    return _evaluate(ast);\n}\n", "import { binaryOperators, comparators } from \"./py_tokenizer\";\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef { import(\"./py_tokenizer\").Token } Token\n */\n\n/**\n * @typedef {{type: 0, value: number}} ASTNumber\n * @typedef {{type: 1, value: string}} ASTString\n * @typedef {{type: 2, value: boolean}} ASTBoolean\n * @typedef {{type: 3}} ASTNone\n * @typedef {{type: 4, value: AST[]}} ASTList\n * @typedef {{type: 5, value: string}} ASTName\n * @typedef {{type: 6, op: string, right: AST}} ASTUnaryOperator\n * @typedef {{type: 7, op: string, left: AST, right: AST}} ASTBinaryOperator\n * @typedef {{type: 8, fn: AST, args: AST[], kwargs: {[key: string]: AST}}} ASTFunctionCall\n * @typedef {{type: 9, name: ASTName, value: AST}} ASTAssignment\n * @typedef {{type: 10, value: AST[]}} ASTTuple\n * @typedef {{type: 11, value: { [key: string]: AST}}} ASTDictionary\n * @typedef {{type: 12, target: AST, key: AST}} ASTLookup\n * @typedef {{type: 13, condition: AST, ifTrue: AST, ifFalse: AST}} ASTIf\n * @typedef {{type: 14, op: string, left: AST, right: AST}} ASTBooleanOperator\n * @typedef {{type: 15, obj: AST, key: string}} ASTObjLookup\n *\n * @typedef { ASTNumber | ASTString | ASTBoolean | ASTNone | ASTList | ASTName | ASTUnaryOperator | ASTBinaryOperator | ASTFunctionCall | ASTAssignment | ASTTuple | ASTDictionary |ASTLookup | ASTIf | ASTBooleanOperator | ASTObjLookup} AST\n */\n\nexport class ParserError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Constants and helpers\n// -----------------------------------------------------------------------------\n\nconst chainedOperators = new Set(comparators);\nconst infixOperators = new Set(binaryOperators.concat(comparators));\n\n/**\n * Compute the \"binding power\" of a symbol\n *\n * @param {string} symbol\n * @returns {number}\n */\nexport function bp(symbol) {\n    switch (symbol) {\n        case \"=\":\n            return 10;\n        case \"if\":\n            return 20;\n        case \"in\":\n        case \"not in\":\n        case \"is\":\n        case \"is not\":\n        case \"<\":\n        case \"<=\":\n        case \">\":\n        case \">=\":\n        case \"<>\":\n        case \"==\":\n        case \"!=\":\n            return 60;\n        case \"or\":\n            return 30;\n        case \"and\":\n            return 40;\n        case \"not\":\n            return 50;\n        case \"|\":\n            return 70;\n        case \"^\":\n            return 80;\n        case \"&\":\n            return 90;\n        case \"<<\":\n        case \">>\":\n            return 100;\n        case \"+\":\n        case \"-\":\n            return 110;\n        case \"*\":\n        case \"/\":\n        case \"//\":\n        case \"%\":\n            return 120;\n        case \"**\":\n            return 140;\n        case \".\":\n        case \"(\":\n        case \"[\":\n            return 150;\n    }\n    return 0;\n}\n\n/**\n * Compute binding power of a symbol\n *\n * @param {Token} token\n * @returns {number}\n */\nfunction bindingPower(token) {\n    return token.type === 2 /* Symbol */ ? bp(token.value) : 0;\n}\n\n/**\n * Check if a token is a symbol of a given value\n *\n * @param {Token} token\n * @param {string} value\n * @returns {boolean}\n */\nfunction isSymbol(token, value) {\n    return token.type === 2 /* Symbol */ && token.value === value;\n}\n\n/**\n * @param {Token} current\n * @param {Token[]} tokens\n * @returns {AST}\n */\nfunction parsePrefix(current, tokens) {\n    switch (current.type) {\n        case 0 /* Number */:\n            return { type: 0 /* Number */, value: current.value };\n        case 1 /* String */:\n            return { type: 1 /* String */, value: current.value };\n        case 4 /* Constant */:\n            if (current.value === \"None\") {\n                return { type: 3 /* None */ };\n            } else {\n                return { type: 2 /* Boolean */, value: current.value === \"True\" };\n            }\n        case 3 /* Name */:\n            return { type: 5 /* Name */, value: current.value };\n        case 2 /* Symbol */:\n            switch (current.value) {\n                case \"-\":\n                case \"+\":\n                case \"~\":\n                    return {\n                        type: 6 /* UnaryOperator */,\n                        op: current.value,\n                        right: _parse(tokens, 130),\n                    };\n                case \"not\":\n                    return {\n                        type: 6 /* UnaryOperator */,\n                        op: current.value,\n                        right: _parse(tokens, 50),\n                    };\n                case \"(\": {\n                    const content = [];\n                    let isTuple = false;\n                    while (tokens[0] && !isSymbol(tokens[0], \")\")) {\n                        content.push(_parse(tokens, 0));\n                        if (tokens[0]) {\n                            if (tokens[0] && isSymbol(tokens[0], \",\")) {\n                                isTuple = true;\n                                tokens.shift();\n                            } else if (!isSymbol(tokens[0], \")\")) {\n                                throw new ParserError(\"parsing error\");\n                            }\n                        } else {\n                            throw new ParserError(\"parsing error\");\n                        }\n                    }\n                    if (!tokens[0] || !isSymbol(tokens[0], \")\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    isTuple = isTuple || content.length === 0;\n                    return isTuple ? { type: 10 /* Tuple */, value: content } : content[0];\n                }\n                case \"[\": {\n                    const value = [];\n                    while (tokens[0] && !isSymbol(tokens[0], \"]\")) {\n                        value.push(_parse(tokens, 0));\n                        if (tokens[0]) {\n                            if (isSymbol(tokens[0], \",\")) {\n                                tokens.shift();\n                            } else if (!isSymbol(tokens[0], \"]\")) {\n                                throw new ParserError(\"parsing error\");\n                            }\n                        }\n                    }\n                    if (!tokens[0] || !isSymbol(tokens[0], \"]\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    return { type: 4 /* List */, value };\n                }\n                case \"{\": {\n                    const dict = {};\n                    while (tokens[0] && !isSymbol(tokens[0], \"}\")) {\n                        const key = _parse(tokens, 0);\n                        if (\n                            (key.type !== 1 /* String */ && key.type !== 0) /* Number */ ||\n                            !tokens[0] ||\n                            !isSymbol(tokens[0], \":\")\n                        ) {\n                            throw new ParserError(\"parsing error\");\n                        }\n                        tokens.shift();\n                        const value = _parse(tokens, 0);\n                        dict[key.value] = value;\n                        if (isSymbol(tokens[0], \",\")) {\n                            tokens.shift();\n                        }\n                    }\n                    // remove the } token\n                    if (!tokens.shift()) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    return { type: 11 /* Dictionary */, value: dict };\n                }\n            }\n    }\n    throw new ParserError(\"Token cannot be parsed\");\n}\n\n/**\n * @param {AST} ast\n * @param {Token} current\n * @param {Token[]} tokens\n * @returns {AST}\n */\nfunction parseInfix(left, current, tokens) {\n    switch (current.type) {\n        case 2 /* Symbol */:\n            if (infixOperators.has(current.value)) {\n                let right = _parse(tokens, bindingPower(current));\n                if (current.value === \"and\" || current.value === \"or\") {\n                    return {\n                        type: 14 /* BooleanOperator */,\n                        op: current.value,\n                        left,\n                        right,\n                    };\n                } else if (current.value === \".\") {\n                    if (right.type === 5 /* Name */) {\n                        return {\n                            type: 15 /* ObjLookup */,\n                            obj: left,\n                            key: right.value,\n                        };\n                    } else {\n                        throw new ParserError(\"invalid obj lookup\");\n                    }\n                }\n                let op = {\n                    type: 7 /* BinaryOperator */,\n                    op: current.value,\n                    left,\n                    right,\n                };\n                while (\n                    chainedOperators.has(current.value) &&\n                    tokens[0] &&\n                    tokens[0].type === 2 /* Symbol */ &&\n                    chainedOperators.has(tokens[0].value)\n                ) {\n                    const nextToken = tokens.shift();\n                    op = {\n                        type: 14 /* BooleanOperator */,\n                        op: \"and\",\n                        left: op,\n                        right: {\n                            type: 7 /* BinaryOperator */,\n                            op: nextToken.value,\n                            left: right,\n                            right: _parse(tokens, bindingPower(nextToken)),\n                        },\n                    };\n                    right = op.right.right;\n                }\n                return op;\n            }\n            switch (current.value) {\n                case \"(\": {\n                    // function call\n                    const args = [];\n                    const kwargs = {};\n                    while (tokens[0] && !isSymbol(tokens[0], \")\")) {\n                        const arg = _parse(tokens, 0);\n                        if (arg.type === 9 /* Assignment */) {\n                            kwargs[arg.name.value] = arg.value;\n                        } else {\n                            args.push(arg);\n                        }\n                        if (tokens[0] && isSymbol(tokens[0], \",\")) {\n                            tokens.shift();\n                        }\n                    }\n                    if (!tokens[0] || !isSymbol(tokens[0], \")\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    return { type: 8 /* FunctionCall */, fn: left, args, kwargs };\n                }\n                case \"=\":\n                    if (left.type === 5 /* Name */) {\n                        return {\n                            type: 9 /* Assignment */,\n                            name: left,\n                            value: _parse(tokens, 10),\n                        };\n                    }\n                    break;\n                case \"[\": {\n                    // lookup in dictionary\n                    const key = _parse(tokens);\n                    if (!tokens[0] || !isSymbol(tokens[0], \"]\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    return {\n                        type: 12 /* Lookup */,\n                        target: left,\n                        key: key,\n                    };\n                }\n                case \"if\": {\n                    const condition = _parse(tokens);\n                    if (!tokens[0] || !isSymbol(tokens[0], \"else\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    const ifFalse = _parse(tokens);\n                    return {\n                        type: 13 /* If */,\n                        condition,\n                        ifTrue: left,\n                        ifFalse,\n                    };\n                }\n            }\n    }\n    throw new ParserError(\"Token cannot be parsed\");\n}\n\n/**\n * @param {Token[]} tokens\n * @param {number} [bp]\n * @returns {AST}\n */\nfunction _parse(tokens, bp = 0) {\n    const token = tokens.shift();\n    let expr = parsePrefix(token, tokens);\n    while (tokens[0] && bindingPower(tokens[0]) > bp) {\n        expr = parseInfix(expr, tokens.shift(), tokens);\n    }\n    return expr;\n}\n\n// -----------------------------------------------------------------------------\n// Parse function\n// -----------------------------------------------------------------------------\n\n/**\n * Parse a list of tokens\n *\n * @param {Token[]} tokens\n * @returns {AST}\n */\nexport function parse(tokens) {\n    if (tokens.length) {\n        const ast = _parse(tokens, 0);\n        if (tokens.length) {\n            throw new ParserError(\"Token(s) unused\");\n        }\n        return ast;\n    }\n    throw new ParserError(\"Missing token\");\n}\n\n/**\n * @param {any[]} args\n * @param {string[]} spec\n * @returns {{[name: string]: any}}\n */\nexport function parseArgs(args, spec) {\n    const last = args[args.length - 1];\n    const unnamedArgs = typeof last === \"object\" ? args.slice(0, -1) : args;\n    const kwargs = typeof last === \"object\" ? last : {};\n    for (const [index, val] of unnamedArgs.entries()) {\n        kwargs[spec[index]] = val;\n    }\n    return kwargs;\n}\n", "// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef {{type: 0, value: number}} TokenNumber\n *\n * @typedef {{type: 1, value: string}} TokenString\n *\n * @typedef {{type: 2, value: string}} TokenSymbol\n *\n * @typedef {{type: 3, value: string}} TokenName\n *\n * @typedef {{type: 4, value: string}} TokenConstant\n *\n * @typedef {TokenNumber | TokenString | TokenSymbol | TokenName | TokenConstant} Token\n */\n\nexport class TokenizerError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Helpers and Constants\n// -----------------------------------------------------------------------------\n\n/**\n * Directly maps a single escape code to an output character\n */\nconst directMap = {\n    \"\\\\\": \"\\\\\",\n    '\"': '\"',\n    \"'\": \"'\",\n    a: \"\\x07\",\n    b: \"\\x08\",\n    f: \"\\x0c\",\n    n: \"\\n\",\n    r: \"\\r\",\n    t: \"\\t\",\n    v: \"\\v\",\n};\n\n/**\n * Implements the decoding of Python string literals (embedded in\n * JS strings) into actual JS strings. This includes the decoding\n * of escapes into their corresponding JS\n * characters/codepoints/whatever.\n *\n * The ``unicode`` flags notes whether the literal should be\n * decoded as a bytestring literal or a unicode literal, which\n * pretty much only impacts decoding (or not) of unicode escapes\n * at this point since bytestrings are not technically handled\n * (everything is decoded to JS \"unicode\" strings)\n *\n * Eventurally, ``str`` could eventually use typed arrays, that'd\n * be interesting...\n *\n * @param {string} str\n * @param {boolean} unicode\n * @returns {string}\n */\nfunction decodeStringLiteral(str, unicode) {\n    const out = [];\n    let code;\n    for (var i = 0; i < str.length; ++i) {\n        if (str[i] !== \"\\\\\") {\n            out.push(str[i]);\n            continue;\n        }\n        var escape = str[i + 1];\n        if (escape in directMap) {\n            out.push(directMap[escape]);\n            ++i;\n            continue;\n        }\n        switch (escape) {\n            // Ignored\n            case \"\\n\":\n                ++i;\n                continue;\n            // Character named name in the Unicode database (Unicode only)\n            case \"N\":\n                if (!unicode) {\n                    break;\n                }\n                throw new TokenizerError(\"SyntaxError: \\\\N{} escape not implemented\");\n            case \"u\":\n                if (!unicode) {\n                    break;\n                }\n                var uni = str.slice(i + 2, i + 6);\n                if (!/[0-9a-f]{4}/i.test(uni)) {\n                    throw new TokenizerError(\n                        [\n                            \"SyntaxError: (unicode error) 'unicodeescape' codec\",\n                            \" can't decode bytes in position \",\n                            i,\n                            \"-\",\n                            i + 4,\n                            \": truncated \\\\uXXXX escape\",\n                        ].join(\"\")\n                    );\n                }\n                code = parseInt(uni, 16);\n                out.push(String.fromCharCode(code));\n                // escape + 4 hex digits\n                i += 5;\n                continue;\n            case \"U\":\n                if (!unicode) {\n                    break;\n                }\n                // TODO: String.fromCodePoint\n                throw new TokenizerError(\"SyntaxError: \\\\U escape not implemented\");\n            case \"x\":\n                // get 2 hex digits\n                var hex = str.slice(i + 2, i + 4);\n                if (!/[0-9a-f]{2}/i.test(hex)) {\n                    if (!unicode) {\n                        throw new TokenizerError(\"ValueError: invalid \\\\x escape\");\n                    }\n                    throw new TokenizerError(\n                        [\n                            \"SyntaxError: (unicode error) 'unicodeescape'\",\n                            \" codec can't decode bytes in position \",\n                            i,\n                            \"-\",\n                            i + 2,\n                            \": truncated \\\\xXX escape\",\n                        ].join(\"\")\n                    );\n                }\n                code = parseInt(hex, 16);\n                out.push(String.fromCharCode(code));\n                // skip escape + 2 hex digits\n                i += 3;\n                continue;\n            default:\n                // Check if octal\n                if (!/[0-8]/.test(escape)) {\n                    break;\n                }\n                var r = /[0-8]{1,3}/g;\n                r.lastIndex = i + 1;\n                var m = r.exec(str);\n                var oct = m[0];\n                code = parseInt(oct, 8);\n                out.push(String.fromCharCode(code));\n                // skip matchlength\n                i += oct.length;\n                continue;\n        }\n        out.push(\"\\\\\");\n    }\n    return out.join(\"\");\n}\n\nconst constants = new Set([\"None\", \"False\", \"True\"]);\n\nexport const comparators = [\n    \"in\",\n    \"not\",\n    \"not in\",\n    \"is\",\n    \"is not\",\n    \"<\",\n    \"<=\",\n    \">\",\n    \">=\",\n    \"<>\",\n    \"!=\",\n    \"==\",\n];\n\nexport const binaryOperators = [\n    \"or\",\n    \"and\",\n    \"|\",\n    \"^\",\n    \"&\",\n    \"<<\",\n    \">>\",\n    \"+\",\n    \"-\",\n    \"*\",\n    \"/\",\n    \"//\",\n    \"%\",\n    \"~\",\n    \"**\",\n    \".\",\n];\n\nexport const unaryOperators = [\"-\"];\n\nconst symbols = new Set([\n    ...[\"(\", \")\", \"[\", \"]\", \"{\", \"}\", \":\", \",\"],\n    ...[\"if\", \"else\", \"lambda\", \"=\"],\n    ...comparators,\n    ...binaryOperators,\n    ...unaryOperators,\n]);\n\n// Regexps\nfunction group(...args) {\n    return \"(\" + args.join(\"|\") + \")\";\n}\n\nconst Name = \"[a-zA-Z_]\\\\w*\";\nconst Whitespace = \"[ \\\\f\\\\t]*\";\nconst DecNumber = \"\\\\d+(L|l)?\";\nconst IntNumber = DecNumber;\n\nconst Exponent = \"[eE][+-]?\\\\d+\";\nconst PointFloat = group(`\\\\d+\\\\.\\\\d*(${Exponent})?`, `\\\\.\\\\d+(${Exponent})?`);\n// Exponent not optional when no decimal point\nconst FloatNumber = group(PointFloat, `\\\\d+${Exponent}`);\n\nconst Number = group(FloatNumber, IntNumber);\nconst Operator = group(\"\\\\*\\\\*=?\", \">>=?\", \"<<=?\", \"<>\", \"!=\", \"//=?\", \"[+\\\\-*/%&|^=<>]=?\", \"~\");\nconst Bracket = \"[\\\\[\\\\]\\\\(\\\\)\\\\{\\\\}]\";\nconst Special = \"[:;.,`@]\";\nconst Funny = group(Operator, Bracket, Special);\nconst ContStr = group(\n    \"([uU])?'([^\\n'\\\\\\\\]*(?:\\\\\\\\.[^\\n'\\\\\\\\]*)*)'\",\n    '([uU])?\"([^\\n\"\\\\\\\\]*(?:\\\\\\\\.[^\\n\"\\\\\\\\]*)*)\"'\n);\nconst PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);\nconst NumberPattern = new RegExp(\"^\" + Number + \"$\");\nconst StringPattern = new RegExp(\"^\" + ContStr + \"$\");\nconst NamePattern = new RegExp(\"^\" + Name + \"$\");\nconst strip = new RegExp(\"^\" + Whitespace);\n\n// -----------------------------------------------------------------------------\n// Tokenize function\n// -----------------------------------------------------------------------------\n\n/**\n * Transform a string into a list of tokens\n *\n * @param {string} str\n * @returns {Token[]}\n */\nexport function tokenize(str) {\n    const tokens = [];\n    const max = str.length;\n    let start = 0;\n    let end = 0;\n    // /g flag makes repeated exec() have memory\n    const pseudoprog = new RegExp(PseudoToken, \"g\");\n    while (pseudoprog.lastIndex < max) {\n        const pseudomatch = pseudoprog.exec(str);\n        if (!pseudomatch) {\n            // if match failed on trailing whitespace, end tokenizing\n            if (/^\\s+$/.test(str.slice(end))) {\n                break;\n            }\n            throw new TokenizerError(\n                \"Failed to tokenize <<\" +\n                    str +\n                    \">> at index \" +\n                    (end || 0) +\n                    \"; parsed so far: \" +\n                    tokens\n            );\n        }\n        if (pseudomatch.index > end) {\n            if (str.slice(end, pseudomatch.index).trim()) {\n                throw new TokenizerError(\"Invalid expression\");\n            }\n        }\n        start = pseudomatch.index;\n        end = pseudoprog.lastIndex;\n        let token = str.slice(start, end).replace(strip, \"\");\n        if (NumberPattern.test(token)) {\n            tokens.push({\n                type: 0 /* Number */,\n                value: parseFloat(token),\n            });\n        } else if (StringPattern.test(token)) {\n            var m = StringPattern.exec(token);\n            tokens.push({\n                type: 1 /* String */,\n                value: decodeStringLiteral(m[3] !== undefined ? m[3] : m[5], !!(m[2] || m[4])),\n            });\n        } else if (symbols.has(token)) {\n            // transform 'not in' and 'is not' in a single token\n            if (token === \"in\" && tokens.length > 0 && tokens[tokens.length - 1].value === \"not\") {\n                token = \"not in\";\n                tokens.pop();\n            } else if (\n                token === \"not\" &&\n                tokens.length > 0 &&\n                tokens[tokens.length - 1].value === \"is\"\n            ) {\n                token = \"is not\";\n                tokens.pop();\n            }\n            tokens.push({\n                type: 2 /* Symbol */,\n                value: token,\n            });\n        } else if (constants.has(token)) {\n            tokens.push({\n                type: 4 /* Constant */,\n                value: token,\n            });\n        } else if (NamePattern.test(token)) {\n            tokens.push({\n                type: 3 /* Name */,\n                value: token,\n            });\n        } else {\n            throw new TokenizerError(\"Invalid expression\");\n        }\n    }\n    return tokens;\n}\n", "import { bp } from \"./py_parser\";\nimport { PyDate, PyDateTime } from \"./py_date\";\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef { import(\"./py_parser\").AST } AST\n */\n\n// -----------------------------------------------------------------------------\n// Utils\n// -----------------------------------------------------------------------------\n\n/**\n * Represent any value as a primitive AST\n *\n * @param {any} value\n * @returns {AST}\n */\nexport function toPyValue(value) {\n    switch (typeof value) {\n        case \"string\":\n            return { type: 1 /* String */, value };\n        case \"number\":\n            return { type: 0 /* Number */, value };\n        case \"boolean\":\n            return { type: 2 /* Boolean */, value };\n        case \"object\":\n            if (Array.isArray(value)) {\n                return { type: 4 /* List */, value: value.map(toPyValue) };\n            } else if (value === null) {\n                return { type: 3 /* None */ };\n            } else if (value instanceof Date) {\n                return { type: 1, value: PyDateTime.convertDate(value) };\n            } else if (value instanceof PyDate || value instanceof PyDateTime) {\n                return { type: 1, value };\n            } else {\n                const content = {};\n                for (const key in value) {\n                    content[key] = toPyValue(value[key]);\n                }\n                return { type: 11 /* Dictionary */, value: content };\n            }\n        default:\n            throw new Error(\"Invalid type\");\n    }\n}\n\n/**\n * @param {AST} ast\n * @param {number} [lbp] left binding power\n * @return {string}\n */\nexport function formatAST(ast, lbp = 0) {\n    switch (ast.type) {\n        case 3 /* None */:\n            return \"None\";\n        case 1 /* String */:\n            return JSON.stringify(ast.value);\n        case 0 /* Number */:\n            return String(ast.value);\n        case 2 /* Boolean */:\n            return ast.value ? \"True\" : \"False\";\n        case 4 /* List */:\n            return `[${ast.value.map(formatAST).join(\", \")}]`;\n        case 6 /* UnaryOperator */:\n            if (ast.op === \"not\") {\n                return `not ` + formatAST(ast.right, 50);\n            }\n            return ast.op + formatAST(ast.right, 130);\n        case 7 /* BinaryOperator */: {\n            const abp = bp(ast.op);\n            const str = `${formatAST(ast.left, abp)} ${ast.op} ${formatAST(ast.right, abp)}`;\n            return abp < lbp ? `(${str})` : str;\n        }\n        case 11 /* Dictionary */: {\n            const pairs = [];\n            for (const k in ast.value) {\n                pairs.push(`\"${k}\": ${formatAST(ast.value[k])}`);\n            }\n            return `{` + pairs.join(\", \") + `}`;\n        }\n        case 10 /* Tuple */:\n            return `(${ast.value.map(formatAST).join(\", \")})`;\n        case 5 /* Name */:\n            return ast.value;\n        case 12 /* Lookup */: {\n            return `${formatAST(ast.target)}[${formatAST(ast.key)}]`;\n        }\n        case 13 /* If */: {\n            const { ifTrue, condition, ifFalse } = ast;\n            return `${formatAST(ifTrue)} if ${formatAST(condition)} else ${formatAST(ifFalse)}`;\n        }\n        case 14 /* BooleanOperator */: {\n            const abp = bp(ast.op);\n            const str = `${formatAST(ast.left, abp)} ${ast.op} ${formatAST(ast.right, abp)}`;\n            return abp < lbp ? `(${str})` : str;\n        }\n        case 15 /* ObjLookup */:\n            return `${formatAST(ast.obj, 150)}.${ast.key}`;\n        case 8 /* FunctionCall */: {\n            const args = ast.args.map(formatAST);\n            const kwargs = [];\n            for (const kwarg in ast.kwargs) {\n                kwargs.push(`${kwarg} = ${formatAST(ast.kwargs[kwarg])}`);\n            }\n            const argStr = args.concat(kwargs).join(\", \");\n            return `${formatAST(ast.fn)}(${argStr})`;\n        }\n    }\n    throw new Error(\"invalid expression: \" + ast);\n}\n\nexport const PY_DICT = Object.create(null);\n\n/**\n * @param {Object} obj\n * @returns {AST} a python dictionary\n */\nexport function toPyDict(obj) {\n    return new Proxy(obj, {\n        getPrototypeOf() {\n            return PY_DICT;\n        },\n    });\n}\n", "import { Component, onWillStart, onWillUpdateProps } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { TagsList } from \"@web/core/tags_list/tags_list\";\nimport { isId } from \"@web/core/tree_editor/utils\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { imageUrl } from \"@web/core/utils/urls\";\nimport { RecordAutocomplete } from \"./record_autocomplete\";\nimport { useTagNavigation } from \"./tag_navigation_hook\";\n\nexport class MultiRecordSelector extends Component {\n    static props = {\n        resIds: { type: Array, element: Number },\n        resModel: String,\n        update: Function,\n        domain: { type: Array, optional: true },\n        context: { type: Object, optional: true },\n        fieldString: { type: String, optional: true },\n        placeholder: { type: String, optional: true },\n    };\n    static components = { RecordAutocomplete, TagsList };\n    static template = \"web.MultiRecordSelector\";\n\n    setup() {\n        this.nameService = useService(\"name\");\n        useTagNavigation(\"multiRecordSelector\", {\n            delete: (index) => this.deleteTag(index),\n        });\n        onWillStart(() => this.computeDerivedParams());\n        onWillUpdateProps((nextProps) => this.computeDerivedParams(nextProps));\n    }\n\n    get isAvatarModel() {\n        // bof\n        return [\"res.partner\", \"res.users\", \"hr.employee\", \"hr.employee.public\"].includes(\n            this.props.resModel\n        );\n    }\n\n    async computeDerivedParams(props = this.props) {\n        const displayNames = await this.getDisplayNames(props);\n        this.tags = this.getTags(props, displayNames);\n    }\n\n    async getDisplayNames(props) {\n        const ids = this.getIds(props);\n        return this.nameService.loadDisplayNames(props.resModel, ids);\n    }\n\n    /**\n     * Placeholder should be empty if there is at least one tag. We cannot use\n     * the default behavior of the input placeholder because even if there is\n     * a tag, the input is still empty.\n     */\n    get placeholder() {\n        return this.getTags(this.props, {}).length ? \"\" : this.props.placeholder;\n    }\n\n    getIds(props = this.props) {\n        return props.resIds;\n    }\n\n    getTags(props, displayNames) {\n        return props.resIds.map((id, index) => {\n            const text =\n                typeof displayNames[id] === \"string\"\n                    ? displayNames[id]\n                    : _t(\"Inaccessible/missing record ID: %s\", id);\n            return {\n                text,\n                onDelete: () => {\n                    this.deleteTag(index);\n                },\n                img:\n                    this.isAvatarModel &&\n                    isId(id) &&\n                    imageUrl(this.props.resModel, id, \"avatar_128\"),\n            };\n        });\n    }\n\n    deleteTag(index) {\n        this.props.update([\n            ...this.props.resIds.slice(0, index),\n            ...this.props.resIds.slice(index + 1),\n        ]);\n    }\n\n    update(resIds) {\n        this.props.update([...this.props.resIds, ...resIds]);\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { AutoComplete } from \"@web/core/autocomplete/autocomplete\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Domain } from \"@web/core/domain\";\nimport { registry } from \"@web/core/registry\";\nimport { useOwnedDialogs, useService } from \"@web/core/utils/hooks\";\n\nconst SEARCH_LIMIT = 7;\nconst SEARCH_MORE_LIMIT = 320;\n\nexport class RecordAutocomplete extends Component {\n    static props = {\n        resModel: String,\n        update: Function,\n        multiSelect: Boolean,\n        getIds: Function,\n        value: String,\n        domain: { type: Array, optional: true },\n        context: { type: Object, optional: true },\n        className: { type: String, optional: true },\n        fieldString: { type: String, optional: true },\n        placeholder: { type: String, optional: true },\n        slots: { optional: true },\n    };\n    static components = { AutoComplete };\n    static template = \"web.RecordAutocomplete\";\n\n    setup() {\n        this.orm = useService(\"orm\");\n        this.nameService = useService(\"name\");\n        this.addDialog = useOwnedDialogs();\n        this.sources = [\n            {\n                placeholder: _t(\"Loading...\"),\n                options: this.loadOptionsSource.bind(this),\n                optionSlot: this.props.slots?.autoCompleteItem ? \"option\" : undefined,\n            },\n        ];\n    }\n\n    addNames(options) {\n        const displayNames = Object.fromEntries(options);\n        this.nameService.addDisplayNames(this.props.resModel, displayNames);\n    }\n\n    getIds() {\n        return this.props.getIds();\n    }\n\n    async loadOptionsSource(name) {\n        if (this.lastProm) {\n            this.lastProm.abort(false);\n        }\n        this.lastProm = this.search(name, SEARCH_LIMIT + 1);\n        const nameGets = (await this.lastProm).map(([id, label]) => [\n            id,\n            label ? label.split(\"\\n\")[0] : _t(\"Unnamed\"),\n        ]);\n        this.addNames(nameGets);\n        const options = nameGets.map(([id, label]) => ({\n            data: {\n                record: { id, display_name: label },\n            },\n            label,\n            onSelect: () => this.props.update([id]),\n        }));\n        if (SEARCH_LIMIT < nameGets.length) {\n            options.push({\n                cssClass: \"o_m2o_dropdown_option\",\n                label: _t(\"Search More...\"),\n                onSelect: this.onSearchMore.bind(this, name),\n            });\n        }\n        if (options.length === 0) {\n            options.push({ label: _t(\"(no result)\") });\n        }\n        return options;\n    }\n\n    async onSearchMore(name) {\n        const { fieldString, multiSelect, resModel } = this.props;\n        let operator;\n        const ids = [];\n        if (name) {\n            const nameGets = await this.search(name, SEARCH_MORE_LIMIT);\n            this.addNames(nameGets);\n            operator = \"in\";\n            ids.push(...nameGets.map((nameGet) => nameGet[0]));\n        } else {\n            operator = \"not in\";\n            ids.push(...this.getIds());\n        }\n        const dynamicFilters = ids.length\n            ? [\n                  {\n                      description: _t(\"Quick search: %s\", name),\n                      domain: [[\"id\", operator, ids]],\n                  },\n              ]\n            : undefined;\n        // fine for now but we don't like this kind of dependence of core to views\n        const SelectCreateDialog = registry.category(\"dialogs\").get(\"select_create\");\n        this.addDialog(SelectCreateDialog, {\n            title: _t(\"Search: %s\", fieldString),\n            dynamicFilters,\n            domain: this.getDomain(),\n            resModel,\n            noCreate: true,\n            multiSelect,\n            context: this.props.context || {},\n            onSelected: (resId) => {\n                const resIds = Array.isArray(resId) ? resId : [resId];\n                this.props.update([...resIds]);\n            },\n        });\n    }\n\n    getDomain() {\n        const domainIds = Domain.not([[\"id\", \"in\", this.getIds()]]);\n        if (this.props.domain) {\n            return Domain.and([this.props.domain, domainIds]).toList();\n        }\n        return domainIds.toList();\n    }\n\n    search(name, limit) {\n        const domain = this.getDomain();\n        return this.orm.call(this.props.resModel, \"name_search\", [], {\n            name,\n            domain: domain,\n            limit,\n            context: this.props.context || {},\n        });\n    }\n\n    onChange({ inputValue }) {\n        if (!inputValue.length) {\n            this.props.update([]);\n        }\n    }\n}\n", "import { Component, onWillStart, onWillUpdateProps } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { isId } from \"@web/core/tree_editor/utils\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { RecordAutocomplete } from \"./record_autocomplete\";\n\nexport class RecordSelector extends Component {\n    static props = {\n        resId: [Number, { value: false }],\n        resModel: String,\n        update: Function,\n        domain: { type: Array, optional: true },\n        context: { type: Object, optional: true },\n        fieldString: { type: String, optional: true },\n        placeholder: { type: String, optional: true },\n    };\n    static components = { RecordAutocomplete };\n    static template = \"web.RecordSelector\";\n\n    setup() {\n        this.nameService = useService(\"name\");\n        onWillStart(() => this.computeDerivedParams());\n        onWillUpdateProps((nextProps) => this.computeDerivedParams(nextProps));\n    }\n\n    get isAvatarModel() {\n        // bof\n        return [\"res.partner\", \"res.users\", \"hr.employee\", \"hr.employee.public\"].includes(\n            this.props.resModel\n        );\n    }\n\n    get hasAvatarImg() {\n        return this.isAvatarModel && isId(this.props.resId);\n    }\n\n    async computeDerivedParams(props = this.props) {\n        const displayNames = await this.getDisplayNames(props);\n        this.displayName = this.getDisplayName(props, displayNames);\n    }\n\n    async getDisplayNames(props) {\n        const ids = this.getIds(props);\n        return this.nameService.loadDisplayNames(props.resModel, ids);\n    }\n\n    getDisplayName(props = this.props, displayNames) {\n        const { resId } = props;\n        if (resId === false) {\n            return \"\";\n        }\n        return typeof displayNames[resId] === \"string\"\n            ? displayNames[resId]\n            : _t(\"Inaccessible/missing record ID: %s\", resId);\n    }\n\n    getIds(props = this.props) {\n        if (props.resId) {\n            return [props.resId];\n        }\n        return [];\n    }\n\n    update(resIds) {\n        this.props.update(resIds[0] || false);\n        this.render(true);\n    }\n}\n", "import { useRef } from \"@odoo/owl\";\nimport { useNavigation } from \"../navigation/navigation\";\n\n/**\n * This hook allows to navigate between tags in a record selector. It also\n * allows to delete tags with the backspace key.\n * It is meant to be used in component which contains both the components\n * `Autocomplete` and `TagList`.\n *\n * @param {string} refName Name of the t-ref which contains the `Autocomplete` and `TagList` components.\n * @param {object} [options]\n * @param {() => boolean} [options.isEnabled]\n * @param {(index: number) => void} [options.delete] Function to be called when a tag is deleted. It should take the index of the tag to delete as parameter.\n */\nexport function useTagNavigation(refName, options = {}) {\n    const tagsContainerRef = useRef(refName);\n\n    const isEnabled = options.isEnabled ?? (() => true);\n\n    const canRemoveTag = (target) =>\n        options.delete && (target.tagName.toLowerCase() !== \"input\" || !target.value);\n\n    const onBackspaceKeydown = (navigator) => {\n        const el = navigator.activeItem.el;\n        if (el.classList.contains(\"o-autocomplete--input\")) {\n            if (!el.value && navigator.items.length > 1) {\n                options.delete(navigator.items.length - 2);\n            }\n        } else {\n            options.delete(navigator.activeItemIndex);\n        }\n        navigator.items.at(-1).setActive();\n    };\n\n    const canNavigateFromInput = (navigator, navNext) => {\n        const el = navigator.activeItem.el;\n        if (el.classList.contains(\"o-autocomplete--input\")) {\n            const menu = tagsContainerRef.el.querySelector(\".o-autocomplete--dropdown-menu\");\n            const index = navNext ? el.value.length : 0;\n            if (el.selectionStart !== index || menu) {\n                return false;\n            }\n        }\n        return true;\n    };\n\n    useNavigation(tagsContainerRef, {\n        getItems: () =>\n            tagsContainerRef.el?.querySelectorAll(\":scope .o_tag, :scope .o-autocomplete--input\") ??\n            [],\n        isNavigationAvailable: ({ navigator, target }) =>\n            isEnabled() && navigator.isFocused && navigator.contains(target),\n        hotkeys: {\n            tab: null,\n            \"shift+tab\": null,\n            home: null,\n            end: null,\n            enter: null,\n            arrowup: null,\n            arrowdown: null,\n            backspace: {\n                bypassEditableProtection: true,\n                isAvailable: ({ target }) => canRemoveTag(target),\n                callback: (navigator) => onBackspaceKeydown(navigator),\n            },\n            arrowleft: {\n                bypassEditableProtection: true,\n                isAvailable: ({ navigator }) => canNavigateFromInput(navigator, false),\n                callback: (navigator) => navigator.previous(),\n            },\n            arrowright: {\n                bypassEditableProtection: true,\n                isAvailable: ({ navigator }) => canNavigateFromInput(navigator, true),\n                callback: (navigator) => navigator.next(),\n            },\n        },\n    });\n}\n", "import { EventBus, validate } from \"@odoo/owl\";\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\nexport class KeyNotFoundError extends Error {}\n\nexport class DuplicatedKeyError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Validation\n// -----------------------------------------------------------------------------\n\nconst validateSchema = (name, key, value, schema) => {\n    if (!odoo.debug) {\n        return;\n    }\n    try {\n        validate(value, schema);\n    } catch (error) {\n        throw new Error(`Validation error for key \"${key}\" in registry \"${name}\": ${error}`);\n    }\n};\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @template S\n * @template C\n * @typedef {import(\"registries\").RegistryData<S, C>} RegistryData\n */\n\n/**\n * @template T\n * @typedef {T extends RegistryData<any, any> ? T : RegistryData<T, {}>} ToRegistryData\n */\n\n/**\n * @template T\n * @typedef {ToRegistryData<T>[\"__itemShape\"]} GetRegistryItemShape\n */\n\n/**\n * @template T\n * @typedef {ToRegistryData<T>[\"__categories\"]} GetRegistryCategories\n */\n\n/**\n * Registry\n *\n * The Registry class is basically just a mapping from a string key to an object.\n * It is really not much more than an object. It is however useful for the\n * following reasons:\n *\n * 1. it let us react and execute code when someone add something to the registry\n *   (for example, the FunctionRegistry subclass this for this purpose)\n * 2. it throws an error when the get operation fails\n * 3. it provides a chained API to add items to the registry.\n *\n * @template T\n */\nexport class Registry extends EventBus {\n    /**\n     * @param {string} [name]\n     */\n    constructor(name) {\n        super();\n        /** @type {Record<string, [number, GetRegistryItemShape<T>]>}*/\n        this.content = {};\n        /** @type {{ [P in keyof GetRegistryCategories<T>]?: Registry<GetRegistryCategories<T>[P]> }} */\n        this.subRegistries = {};\n        /** @type {GetRegistryItemShape<T>[]}*/\n        this.elements = null;\n        /** @type {[string, GetRegistryItemShape<T>][]}*/\n        this.entries = null;\n        this.name = name;\n        this.validationSchema = null;\n\n        this.addEventListener(\"UPDATE\", () => {\n            this.elements = null;\n            this.entries = null;\n        });\n    }\n\n    /**\n     * Add an entry (key, value) to the registry if key is not already used. If\n     * the parameter force is set to true, an entry with same key (if any) is replaced.\n     *\n     * Note that this also returns the registry, so another add method call can\n     * be chained\n     *\n     * @param {string} key\n     * @param {GetRegistryItemShape<T>} value\n     * @param {{force?: boolean, sequence?: number}} [options]\n     * @returns {Registry<T>}\n     */\n    add(key, value, { force, sequence } = {}) {\n        if (this.validationSchema) {\n            validateSchema(this.name, key, value, this.validationSchema);\n        }\n        if (!force && key in this.content) {\n            throw new DuplicatedKeyError(\n                `Cannot add key \"${key}\" in the \"${this.name}\" registry: it already exists`\n            );\n        }\n        let previousSequence;\n        if (force) {\n            const elem = this.content[key];\n            previousSequence = elem && elem[0];\n        }\n        sequence = sequence === undefined ? previousSequence || 50 : sequence;\n        this.content[key] = [sequence, value];\n        const payload = { operation: \"add\", key, value };\n        this.trigger(\"UPDATE\", payload);\n        return this;\n    }\n\n    /**\n     * Get an item from the registry\n     *\n     * @param {string} key\n     * @returns {GetRegistryItemShape<T>}\n     */\n    get(key, defaultValue) {\n        if (arguments.length < 2 && !(key in this.content)) {\n            throw new KeyNotFoundError(`Cannot find key \"${key}\" in the \"${this.name}\" registry`);\n        }\n        const info = this.content[key];\n        return info ? info[1] : defaultValue;\n    }\n\n    /**\n     * Check the presence of a key in the registry\n     *\n     * @param {string} key\n     * @returns {boolean}\n     */\n    contains(key) {\n        return key in this.content;\n    }\n\n    /**\n     * Get a list of all elements in the registry. Note that it is ordered\n     * according to the sequence numbers.\n     *\n     * @returns {GetRegistryItemShape<T>[]}\n     */\n    getAll() {\n        if (!this.elements) {\n            const content = Object.values(this.content).sort((el1, el2) => el1[0] - el2[0]);\n            this.elements = content.map((elem) => elem[1]);\n        }\n        return this.elements.slice();\n    }\n\n    /**\n     * Return a list of all entries, ordered by sequence numbers.\n     *\n     * @returns {[string, GetRegistryItemShape<T>][]}\n     */\n    getEntries() {\n        if (!this.entries) {\n            const entries = Object.entries(this.content).sort((el1, el2) => el1[1][0] - el2[1][0]);\n            this.entries = entries.map(([str, elem]) => [str, elem[1]]);\n        }\n        return this.entries.slice();\n    }\n\n    /**\n     * Remove an item from the registry\n     *\n     * @param {string} key\n     */\n    remove(key) {\n        const value = this.content[key];\n        delete this.content[key];\n        const payload = { operation: \"delete\", key, value };\n        this.trigger(\"UPDATE\", payload);\n    }\n\n    /**\n     * Open a sub registry (and create it if necessary)\n     *\n     * @template {keyof GetRegistryCategories<T> & string} K\n     * @param {K} subcategory\n     * @returns {Registry<GetRegistryCategories<T>[K]>}\n     */\n    category(subcategory) {\n        if (!(subcategory in this.subRegistries)) {\n            this.subRegistries[subcategory] = new Registry(subcategory);\n        }\n        return this.subRegistries[subcategory];\n    }\n\n    addValidation(schema) {\n        if (this.validationSchema) {\n            throw new Error(\"Validation schema already set on this registry\");\n        }\n        this.validationSchema = schema;\n        for (const [key, value] of this.getEntries()) {\n            validateSchema(this.name, key, value, schema);\n        }\n    }\n}\n\n/** @type {Registry<import(\"registries\").GlobalRegistry>} */\nexport const registry = new Registry();\n", "import { useState, onWillStart, onWillDestroy } from \"@odoo/owl\";\n\nexport function useRegistry(registry) {\n    const state = useState({ entries: registry.getEntries() });\n    const listener = ({ detail }) => {\n        const index = state.entries.findIndex(([k]) => k === detail.key);\n        if (detail.operation === \"add\" && index === -1) {\n            // push the new entry at the right place\n            const newEntries = registry.getEntries();\n            const newEntry = newEntries.find(([k]) => k === detail.key);\n            const newIndex = newEntries.indexOf(newEntry);\n            if (newIndex === newEntries.length - 1) {\n                state.entries.push(newEntry);\n            } else {\n                state.entries.splice(newIndex, 0, newEntry);\n            }\n        } else if (detail.operation === \"delete\" && index >= 0) {\n            state.entries.splice(index, 1);\n        }\n    };\n\n    onWillStart(() => registry.addEventListener(\"UPDATE\", listener));\n    onWillDestroy(() => registry.removeEventListener(\"UPDATE\", listener));\n    return state;\n}\n", "import {\n    Component,\n    onMounted,\n    onWillUpdateProps,\n    onWillUnmount,\n    useEffect,\n    useExternalListener,\n    useRef,\n    useComponent,\n} from \"@odoo/owl\";\n\nfunction useResizable({\n    containerRef,\n    handleRef,\n    initialWidth = 400,\n    getMinWidth = () => 400,\n    onResize = () => {},\n    getResizeSide = () => \"end\",\n}) {\n    containerRef = typeof containerRef == \"string\" ? useRef(containerRef) : containerRef;\n    handleRef = typeof handleRef == \"string\" ? useRef(handleRef) : handleRef;\n    const props = useComponent().props;\n\n    let minWidth = getMinWidth(props);\n    let resizeSide = getResizeSide(props);\n    let isChangingSize = false;\n\n    useExternalListener(document, \"mouseup\", () => onMouseUp());\n    useExternalListener(document, \"mousemove\", (ev) => onMouseMove(ev));\n\n    useExternalListener(window, \"resize\", () => {\n        const limit = getLimitWidth();\n        if (getContainerRect().width >= limit) {\n            resize(computeFinalWidth(limit));\n        }\n    });\n\n    let docDirection;\n    useEffect(\n        (container) => {\n            if (container) {\n                docDirection = getComputedStyle(container).direction;\n            }\n        },\n        () => [containerRef.el]\n    );\n\n    onMounted(() => {\n        if (handleRef.el) {\n            resize(initialWidth);\n            handleRef.el.addEventListener(\"mousedown\", onMouseDown);\n        }\n    });\n\n    onWillUpdateProps((nextProps) => {\n        minWidth = getMinWidth(nextProps);\n        resizeSide = getResizeSide(nextProps);\n    });\n\n    onWillUnmount(() => {\n        if (handleRef.el) {\n            handleRef.el.removeEventListener(\"mousedown\", onMouseDown);\n        }\n    });\n\n    function onMouseDown() {\n        isChangingSize = true;\n        document.body.classList.add(\"pe-none\", \"user-select-none\");\n    }\n\n    function onMouseUp() {\n        isChangingSize = false;\n        document.body.classList.remove(\"pe-none\", \"user-select-none\");\n    }\n\n    function onMouseMove(ev) {\n        if (!isChangingSize || !containerRef.el) {\n            return;\n        }\n        const direction =\n            (docDirection === \"ltr\" && resizeSide === \"end\") ||\n            (docDirection === \"rtl\" && resizeSide === \"start\")\n                ? 1\n                : -1;\n        const fixedSide = direction === 1 ? \"left\" : \"right\";\n        const containerRect = getContainerRect();\n        const newWidth = (ev.clientX - containerRect[fixedSide]) * direction;\n        resize(computeFinalWidth(newWidth));\n    }\n\n    function computeFinalWidth(targetContainerWidth) {\n        const handlerSpacing = handleRef.el ? handleRef.el.offsetWidth / 2 : 10;\n        const w = Math.max(minWidth, targetContainerWidth + handlerSpacing);\n        const limit = getLimitWidth();\n        return Math.min(w, limit - handlerSpacing);\n    }\n\n    function getContainerRect() {\n        const container = containerRef.el;\n        const offsetParent = container.offsetParent;\n        let containerRect = {};\n        if (!offsetParent) {\n            containerRect = container.getBoundingClientRect();\n        } else {\n            containerRect.left = container.offsetLeft;\n            containerRect.right = container.offsetLeft + container.offsetWidth;\n            containerRect.width = container.offsetWidth;\n        }\n        return containerRect;\n    }\n\n    function getLimitWidth() {\n        const offsetParent = containerRef.el.offsetParent;\n        return offsetParent ? offsetParent.offsetWidth : window.innerWidth;\n    }\n\n    function resize(width) {\n        containerRef.el.style.setProperty(\"width\", `${width}px`);\n        onResize(width);\n    }\n}\n\nexport class ResizablePanel extends Component {\n    static template = \"web_studio.ResizablePanel\";\n\n    static components = {};\n    static props = {\n        onResize: { type: Function, optional: true },\n        initialWidth: { type: Number, optional: true },\n        minWidth: { type: Number, optional: true },\n        class: { type: String, optional: true },\n        slots: { type: Object },\n        handleSide: {\n            validate: (val) => [\"start\", \"end\"].includes(val),\n            optional: true,\n        },\n    };\n    static defaultProps = {\n        onResize: () => {},\n        width: 400,\n        minWidth: 400,\n        class: \"\",\n        handleSide: \"end\",\n    };\n\n    setup() {\n        useResizable({\n            containerRef: \"containerRef\",\n            handleRef: \"handleRef\",\n            onResize: this.props.onResize,\n            initialWidth: this.props.initialWidth,\n            getMinWidth: (props) => props.minWidth,\n            getResizeSide: (props) => props.handleSide,\n        });\n    }\n\n    get class() {\n        const classes = this.props.class.split(\" \");\n        if (!classes.some((cls) => cls.startsWith(\"position-\"))) {\n            classes.push(\"position-relative\");\n        }\n        return classes.join(\" \");\n    }\n}\n", "import { Component, onWillUpdateProps, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { useDropdownState } from \"@web/core/dropdown/dropdown_hooks\";\nimport { TagsList } from \"@web/core/tags_list/tags_list\";\nimport { mergeClasses } from \"@web/core/utils/classname\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\nimport { scrollTo } from \"@web/core/utils/scrolling\";\nimport { fuzzyLookup } from \"@web/core/utils/search\";\nimport { useDebounced } from \"@web/core/utils/timing\";\nimport { hasTouch } from \"@web/core/browser/feature_detection\";\n\nlet selectMenuId = 0;\n\nexport const DEBOUNCED_DELAY = 250;\n\nexport class SelectMenu extends Component {\n    static template = \"web.SelectMenu\";\n    static choiceItemTemplate = \"web.SelectMenu.ChoiceItem\";\n\n    static components = { Dropdown, DropdownItem, TagsList };\n\n    static defaultProps = {\n        value: undefined,\n        id: \"\",\n        name: \"\",\n        class: \"\",\n        menuClass: \"\",\n        togglerClass: \"\",\n        multiSelect: false,\n        onSelect: () => {},\n        onNavigated: () => {},\n        onOpened: () => {},\n        onClosed: () => {},\n        required: false,\n        searchable: true,\n        autoSort: true,\n        searchPlaceholder: \"\",\n        choices: [],\n        groups: [],\n        sections: [],\n        disabled: false,\n    };\n\n    static props = {\n        choices: {\n            optional: true,\n            type: Array,\n            element: {\n                type: Object,\n                shape: {\n                    value: true,\n                    label: { type: String },\n                    \"*\": true,\n                },\n            },\n        },\n        groups: {\n            type: Array,\n            optional: true,\n            element: {\n                type: Object,\n                shape: {\n                    label: { type: String, optional: true },\n                    choices: {\n                        type: Array,\n                        element: {\n                            type: Object,\n                            shape: {\n                                value: true,\n                                label: { type: String },\n                                \"*\": true,\n                            },\n                        },\n                    },\n                    section: {\n                        type: String,\n                        optional: true,\n                    },\n                },\n            },\n        },\n        sections: {\n            type: Array,\n            optional: true,\n            element: {\n                label: { type: String },\n                name: { type: String },\n            },\n        },\n        id: { type: String, optional: true },\n        name: { type: String, optional: true },\n        class: { type: String, optional: true },\n        menuClass: { type: String, optional: true },\n        togglerClass: { type: String, optional: true },\n        required: { type: Boolean, optional: true },\n        searchable: { type: Boolean, optional: true },\n        autoSort: { type: Boolean, optional: true },\n        placeholder: { type: String, optional: true },\n        searchPlaceholder: { type: String, optional: true },\n        searchClass: { type: String, optional: true },\n        value: { optional: true },\n        multiSelect: { type: Boolean, optional: true },\n        onInput: { type: Function, optional: true },\n        onSelect: { type: Function, optional: true },\n        onNavigated: { type: Function, optional: true },\n        onOpened: { type: Function, optional: true },\n        onClosed: { type: Function, optional: true },\n        slots: { type: Object, optional: true },\n        disabled: { type: Boolean, optional: true },\n        menuRef: { type: Function, optional: true },\n    };\n\n    static SCROLL_SETTINGS = {\n        defaultCount: 500,\n        increaseAmount: 300,\n        distanceBeforeReload: 500,\n    };\n\n    setup() {\n        this.selectMenuId = selectMenuId++;\n        this.state = useState({\n            choices: [],\n            displayedOptions: [],\n            searchValue: null,\n            isFocused: false,\n        });\n        this.inputRef = useRef(\"inputRef\");\n        this.menuRef = useChildRef();\n        this.props.menuRef?.(this.menuRef);\n        this.debouncedOnInput = useDebounced((ev) => {\n            if (!this.dropdownState.isOpen) {\n                this.dropdownState.open();\n            }\n            const searchString = ev.target.value;\n            this.state.searchValue = searchString;\n            this.onInput(searchString);\n        }, DEBOUNCED_DELAY);\n        this.dropdownState = useDropdownState();\n\n        this.selectedChoice = this.getSelectedChoice(this.props);\n        onWillUpdateProps((nextProps) => {\n            const choicesChanged = this.state.choices !== nextProps.choices;\n            if (choicesChanged) {\n                this.state.choices = nextProps.choices;\n            }\n            if (choicesChanged || this.props.value !== nextProps.value) {\n                this.selectedChoice = this.getSelectedChoice(nextProps);\n            }\n        });\n        useEffect(\n            () => {\n                if (this.dropdownState.isOpen) {\n                    const groups = [{ choices: this.props.choices }, ...this.props.groups];\n                    this.filterOptions(this.state.searchValue, groups);\n                }\n            },\n            () => [this.props.choices, this.props.groups]\n        );\n\n        this.navigationOptions = {\n            shouldFocusFirstItem: !hasTouch(),\n            virtualFocus: this.props.searchable,\n            hotkeys: {\n                enter: {\n                    isAvailable: ({ navigator }) => navigator.items.length > 0,\n                    callback: (navigator) => {\n                        if (navigator.activeItem) {\n                            return navigator.activeItem.select();\n                        }\n                        if (document.activeElement.value) {\n                            navigator.items[0].select();\n                        }\n                    },\n                },\n            },\n            onItemActivated: (element) => {\n                const index = parseInt(element.dataset.choiceIndex);\n                if (index >= 0 && this.state.displayedOptions[index]) {\n                    this.props.onNavigated(this.state.displayedOptions[index]);\n                } else {\n                    this.props.onNavigated();\n                }\n            },\n        };\n    }\n\n    get displayValue() {\n        return this.state.searchValue === null\n            ? this.selectedChoice?.label || \"\"\n            : this.state.searchValue;\n    }\n\n    get displayInputInToggler() {\n        return !this.props.slots || !this.props.slots.default;\n    }\n\n    get displayInputInDropdown() {\n        return (this.isBottomSheet || !this.displayInputInToggler) && this.props.searchable;\n    }\n\n    get isBottomSheet() {\n        return this.env.isSmall && hasTouch();\n    }\n\n    get canDeselect() {\n        return !this.props.required && this.selectedChoice !== undefined;\n    }\n\n    get multiSelectChoices() {\n        return this.selectedChoice.map((c) => ({\n            id: c.value,\n            text: c.label,\n            onDelete: () => {\n                const values = [...this.props.value];\n                values.splice(values.indexOf(c.value), 1);\n                this.props.onSelect(values);\n            },\n        }));\n    }\n\n    get menuClass() {\n        return mergeClasses(\n            {\n                \"my-0\": this.displayInputInToggler,\n                o_select_menu_menu: true,\n                o_select_menu_multi_select: this.props.multiSelect,\n            },\n            this.props.menuClass\n        );\n    }\n\n    get placeholderValue() {\n        if (this.state.isFocused && this.props.searchPlaceholder) {\n            return this.props.searchPlaceholder;\n        }\n        return this.props.placeholder;\n    }\n\n    async onBeforeOpen() {\n        this.onInput(\"\");\n    }\n\n    onInputFocus(ev) {\n        if (!this.props.searchable) {\n            return ev.target.blur();\n        }\n        if (ev.target.classList.contains(\"o_select_menu_input\")) {\n            this.state.isFocused = true;\n            ev.target.select();\n        }\n    }\n\n    onInputBlur(ev) {\n        this.state.isFocused = false;\n        if (ev.target.value === \"\" && this.canDeselect && !this.props.multiSelect) {\n            this.onInputClear();\n        }\n    }\n\n    onInputClick(ev) {\n        if (!ev.target.classList.contains(\"o_select_menu_toggler\")) {\n            ev.stopPropagation();\n        }\n    }\n\n    onInputClear() {\n        this.props.onSelect(null);\n        this.dropdownState.close();\n    }\n\n    onStateChanged(open) {\n        if (open) {\n            if (this.isBottomSheet) {\n                // the toggler input must not be focused\n                document.activeElement.blur();\n            }\n            if (this.displayInputInDropdown && !this.isBottomSheet) {\n                this.inputRef.el.focus();\n            }\n            this.menuRef.el?.addEventListener(\"scroll\", (ev) => this.onScroll(ev));\n            const selectedElement = this.menuRef.el?.querySelectorAll(\".active\")[0];\n            if (selectedElement) {\n                scrollTo(selectedElement);\n            }\n            this.props.onOpened();\n        } else {\n            this.state.searchValue = null;\n            this.props.onClosed();\n        }\n    }\n\n    isOptionSelected(choice) {\n        if (this.props.multiSelect) {\n            return this.props.value.includes(choice.value);\n        }\n        return this.props.value === choice.value;\n    }\n\n    getItemClass(choice) {\n        if (this.isOptionSelected(choice)) {\n            return \"o_select_menu_item fw-bolder active\";\n        } else {\n            return \"o_select_menu_item\";\n        }\n    }\n\n    async onInput(searchString) {\n        this.filterOptions(searchString);\n        if (this.props.onInput) {\n            await this.props.onInput(searchString);\n        }\n    }\n\n    getSelectedChoice(props) {\n        const choices = [...props.choices, ...props.groups.flatMap((g) => g.choices || [])];\n        if (!this.props.multiSelect) {\n            return choices.find((c) => c.value === props.value);\n        }\n\n        const valueSet = new Set(props.value);\n        // Combine previously selected choices + newly selected choice from\n        // the searched choices and then filter the choices based on\n        // props.value i.e. valueSet.\n        return [...(this.selectedChoice || []), ...choices].filter(\n            (c, index, self) =>\n                valueSet.has(c.value) && self.findIndex((t) => t.value === c.value) === index\n        );\n    }\n\n    onItemSelected(value) {\n        if (this.props.multiSelect) {\n            const values = [...this.props.value];\n            const valueIndex = values.indexOf(value);\n\n            if (valueIndex !== -1) {\n                values.splice(valueIndex, 1);\n                this.props.onSelect(values);\n            } else {\n                this.props.onSelect([...this.props.value, value]);\n            }\n        } else if (!this.selectedChoice || this.selectedChoice.value !== value) {\n            this.props.onSelect(value);\n            if (this.inputRef.el) {\n                this.inputRef.el.value = this.state.choices.find((c) => c.value === value).label;\n            }\n        }\n        this.state.searchValue = null;\n    }\n\n    // ==========================================================================================\n    // #                                         Search                                         #\n    // ==========================================================================================\n\n    /**\n     * Filters the choices based on the searchString and\n     * slice the result to display a reasonable amount to\n     * try to prevent any delay when opening the select.\n     *\n     * @param {String} searchString\n     */\n    filterOptions(searchString = \"\", groups) {\n        const groupsList = groups || [\n            { choices: this.props.choices, section: \"\" },\n            ...this.props.groups,\n        ];\n\n        const _choices = [];\n        const _sections = new Set();\n        groupsList.sort((a, b) => (a.section || \"\").localeCompare(b.section || \"\"));\n\n        for (const group of groupsList) {\n            let filteredOptions = group.choices || [];\n\n            if (searchString) {\n                filteredOptions = fuzzyLookup(\n                    searchString.trim(),\n                    filteredOptions,\n                    (choice) => choice.label\n                );\n            } else {\n                if (this.props.autoSort) {\n                    filteredOptions.sort((optionA, optionB) =>\n                        optionA.label.localeCompare(optionB.label)\n                    );\n                }\n            }\n\n            if (filteredOptions.length === 0) {\n                continue;\n            }\n            if (group.section) {\n                const section = this.props.sections.find((e) => e.name === group.section);\n                if (!_sections.has(section)) {\n                    _sections.add(section);\n                    _choices.push({ ...section, isGroup: true });\n                }\n            }\n            if (group.label) {\n                _choices.push({ ...group, isGroup: true });\n            }\n            _choices.push(...filteredOptions);\n        }\n\n        this.state.choices = _choices;\n        this.sliceDisplayedOptions();\n    }\n\n    // ==========================================================================================\n    // #                                         Scroll                                         #\n    // ==========================================================================================\n\n    /**\n     * If the user scrolls to the end of the dropdown,\n     * more choices are loaded.\n     *\n     * @param {*} event\n     */\n    onScroll(event) {\n        const el = event.target;\n        const hasReachMax = this.state.displayedOptions.length >= this.state.choices.length;\n        const remainingDistance = el.scrollHeight - el.scrollTop;\n        const distanceToReload =\n            el.clientHeight + this.constructor.SCROLL_SETTINGS.distanceBeforeReload;\n\n        if (!hasReachMax && remainingDistance < distanceToReload) {\n            const displayCount =\n                this.state.displayedOptions.length +\n                this.constructor.SCROLL_SETTINGS.increaseAmount;\n\n            this.state.displayedOptions = this.state.choices.slice(0, displayCount);\n        }\n    }\n\n    /**\n     * Finds the selected choice and set [displayOptions] to at\n     * least show the selected choice and [defaultCount] more\n     * or show at least the [defaultDisplayCount].\n     */\n    sliceDisplayedOptions() {\n        const selectedIndex = this.getSelectedOptionIndex();\n        const defaultCount = this.constructor.SCROLL_SETTINGS.defaultCount;\n\n        if (selectedIndex === -1) {\n            this.state.displayedOptions = this.state.choices.slice(0, defaultCount);\n        } else {\n            const endIndex = Math.max(\n                selectedIndex + this.constructor.SCROLL_SETTINGS.increaseAmount,\n                defaultCount\n            );\n            this.state.displayedOptions = this.state.choices.slice(0, endIndex);\n        }\n    }\n\n    getSelectedOptionIndex() {\n        let selectedIndex = -1;\n        for (let i = 0; i < this.state.choices.length; i++) {\n            if (this.isOptionSelected(this.state.choices[i])) {\n                selectedIndex = i;\n            }\n        }\n        return selectedIndex;\n    }\n}\n", "/* global SignaturePad */\n\nimport { loadJS } from \"@web/core/assets\";\nimport { isMobileOS } from \"@web/core/browser/feature_detection\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useAutofocus } from \"@web/core/utils/hooks\";\nimport { renderToString } from \"@web/core/utils/render\";\nimport { getDataURLFromFile } from \"@web/core/utils/urls\";\n\nimport { Component, useState, onWillStart, useRef, useEffect } from \"@odoo/owl\";\n\nlet htmlId = 0;\nexport class NameAndSignature extends Component {\n    static template = \"web.NameAndSignature\";\n    static components = { Dropdown, DropdownItem };\n    static props = {\n        signature: { type: Object },\n        defaultFont: { type: String, optional: true },\n        displaySignatureRatio: { type: Number, optional: true },\n        fontColor: { type: String, optional: true },\n        signatureType: { type: String, optional: true },\n        noInputName: { type: Boolean, optional: true },\n        mode: { type: String, optional: true },\n        onSignatureChange: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        defaultFont: \"\",\n        displaySignatureRatio: 3.0,\n        fontColor: \"DarkBlue\",\n        signatureType: \"signature\",\n        noInputName: false,\n        onSignatureChange: () => {},\n    };\n\n    setup() {\n        this.htmlId = htmlId++;\n        this.defaultName = this.props.signature.name || \"\";\n        this.currentFont = 0;\n        this.drawTimeout = null;\n\n        this.state = useState({\n            signMode:\n                this.props.mode || (this.props.noInputName && !this.defaultName ? \"draw\" : \"auto\"),\n            showSignatureArea: !!(this.props.noInputName || this.defaultName),\n            showFontList: false,\n        });\n\n        this.signNameInputRef = useRef(\"signNameInput\");\n        this.signInputLoad = useRef(\"signInputLoad\");\n        useAutofocus({ refName: \"signNameInput\" });\n        useEffect(\n            (el) => {\n                if (el) {\n                    el.click();\n                }\n            },\n            () => [this.signInputLoad.el]\n        );\n\n        onWillStart(async () => {\n            this.fonts = await rpc(`/web/sign/get_fonts/${this.props.defaultFont}`);\n        });\n\n        onWillStart(async () => {\n            await loadJS(\"/web/static/lib/signature_pad/signature_pad.umd.js\");\n        });\n\n        this.signatureRef = useRef(\"signature\");\n        useEffect(\n            (el) => {\n                if (el) {\n                    this.signaturePad = new SignaturePad(el, {\n                        penColor: this.props.fontColor,\n                        backgroundColor: \"rgba(255,255,255,0)\",\n                        minWidth: 2,\n                        maxWidth: 2,\n                    });\n                    this.signaturePad.addEventListener(\"endStroke\", () => {\n                        this.props.signature.isSignatureEmpty = this.isSignatureEmpty;\n                        this.props.onSignatureChange(this.state.signMode);\n                    });\n                    this.resetSignature();\n                    this.props.signature.getSignatureImage = () => this.signaturePad.toDataURL();\n                    this.props.signature.resetSignature = () => this.resetSignature();\n                    if (this.state.signMode === \"auto\") {\n                        this.drawCurrentName();\n                    }\n                    if (this.props.signature.signatureImage) {\n                        this.clear();\n                        this.fromDataURL(this.props.signature.signatureImage);\n                    }\n                }\n            },\n            () => [this.signatureRef.el]\n        );\n    }\n\n    /**\n     * Draws the current name with the current font in the signature field.\n     */\n    async drawCurrentName() {\n        const font = this.fonts[this.currentFont];\n        const text = this.getCleanedName();\n        const canvas = this.signatureRef.el;\n        const img = this.getSVGText(font, text, canvas.width, canvas.height);\n        await this.printImage(img);\n    }\n\n    focusName() {\n        // Don't focus on mobile\n        if (!isMobileOS() && this.signNameInputRef.el) {\n            this.signNameInputRef.el.focus();\n        }\n    }\n\n    /**\n     * Clear the signature field.\n     */\n    clear() {\n        this.signaturePad.clear();\n        this.props.signature.isSignatureEmpty = this.isSignatureEmpty;\n    }\n\n    /**\n    * Loads a signature image from a base64 dataURL and updates the empty state.\n    */\n    async fromDataURL() {\n        await this.signaturePad.fromDataURL(...arguments);\n        this.props.signature.isSignatureEmpty = this.isSignatureEmpty;\n        this.props.onSignatureChange(this.state.signMode);\n    }\n\n    /**\n     * Returns the given name after cleaning it by removing characters that\n     * are not supposed to be used in a signature. If @see signatureType is set\n     * to 'initial', returns the first letter of each word, separated by dots.\n     *\n     * @returns {string} cleaned name\n     */\n    getCleanedName() {\n        // This replaces non-breaking spaces with breaking spaces\n        const text = this.props.signature.name.replace(/\u00a0/g, \" \");\n        if (this.props.signatureType === \"initial\" && text) {\n            return (\n                text\n                    .split(\" \")\n                    .map(function (w) {\n                        return w[0];\n                    })\n                    .join(\".\") + \".\"\n            );\n        }\n        return text;\n    }\n\n    /**\n     * Gets an SVG matching the given parameters, output compatible with the\n     * src attribute of <img/>.\n     *\n     * @private\n     * @param {string} font: base64 encoded font to use\n     * @param {string} text: the name to draw\n     * @param {number} width: the width of the resulting image in px\n     * @param {number} height: the height of the resulting image in px\n     * @returns {string} image = mimetype + image data\n     */\n    getSVGText(font, text, width, height) {\n        const svg = renderToString(\"web.sign_svg_text\", {\n            width: width,\n            height: height,\n            font: font,\n            text: text,\n            type: this.props.signatureType,\n            color: this.props.fontColor,\n        });\n\n        return \"data:image/svg+xml,\" + encodeURI(svg);\n    }\n\n    getSVGTextFont(font) {\n        const height = 100;\n        const width = parseInt(height * this.props.displaySignatureRatio);\n        return this.getSVGText(font, this.getCleanedName(), width, height);\n    }\n\n    uploadFile() {\n        this.signInputLoad.el?.click();\n    }\n\n    /**\n     * Handles change on load file input: displays the loaded image if the\n     * format is correct, or displays an error otherwise.\n     *\n     * @see mode 'load'\n     * @private\n     * @param {Event} ev\n     * @return bool|undefined\n     */\n    async onChangeSignLoadInput(ev) {\n        var file = ev.target.files[0];\n        if (file === undefined) {\n            return false;\n        }\n        if (file.type.substr(0, 5) !== \"image\") {\n            this.clear();\n            this.state.loadIsInvalid = true;\n            return false;\n        }\n        this.state.loadIsInvalid = false;\n\n        const result = await getDataURLFromFile(file);\n        await this.printImage(result);\n    }\n\n    onClickSignAutoSelectStyle() {\n        this.state.showFontList = true;\n    }\n\n    onClickSignDrawClear() {\n        this.clear();\n        this.props.onSignatureChange(this.state.signMode);\n    }\n\n    onClickSignLoad() {\n        this.setMode(\"load\");\n    }\n\n    onClickSignAuto() {\n        this.setMode(\"auto\");\n    }\n\n    onInputSignName(ev) {\n        this.props.signature.name = ev.target.value;\n        if (!this.state.showSignatureArea && this.getCleanedName()) {\n            this.state.showSignatureArea = true;\n            return;\n        }\n        if (this.state.signMode === \"auto\") {\n            this.drawCurrentName();\n        }\n    }\n\n    onSelectFont(index) {\n        this.currentFont = index;\n        this.drawCurrentName();\n    }\n\n    /**\n     * Displays the given image in the signature field.\n     * If needed, resizes the image to fit the existing area.\n     *\n     * @param {string} imgSrc - data of the image to display\n     */\n    async printImage(imgSrc) {\n        this.clear();\n        const c = this.signaturePad.canvas;\n        const img = new Image();\n        img.onload = () => {\n            const ctx = c.getContext(\"2d\");\n            var ratio = ((img.width / img.height) > (c.width / c.height)) ? c.width / img.width : c.height / img.height;\n            ctx.drawImage( \n                img,\n                (c.width / 2) - (img.width * ratio / 2),\n                (c.height / 2) - (img.height * ratio / 2)\n                , img.width * ratio\n                , img.height * ratio\n            );\n            this.props.signature.isSignatureEmpty = this.isSignatureEmpty;\n            this.props.onSignatureChange(this.state.signMode);\n        };\n        img.src = imgSrc;\n        this.signaturePad._isEmpty = false;\n    }\n\n    /**\n     * (Re)initializes the signature area:\n     *  - set the correct width and height of the drawing based on the width\n     *      of the container and the ratio option\n     *  - empty any previous content\n     *  - correctly reset the empty state\n     *  - call @see setMode with reset\n     */\n    resetSignature() {\n        this.resizeSignature();\n        this.clear();\n        this.setMode(this.state.signMode, true);\n        this.focusName();\n    }\n\n    resizeSignature() {\n        // recompute size based on the current width\n        const width = this.signatureRef.el.clientWidth;\n        const height = parseInt(width / this.props.displaySignatureRatio);\n\n        Object.assign(this.signatureRef.el, { width, height });\n    }\n\n    /**\n     * Changes the signature mode. Toggles the display of the relevant\n     * controls and resets the drawing.\n     *\n     * @param {string} mode - the mode to use. Can be one of the following:\n     *  - 'draw': the user draws the signature manually with the mouse\n     *  - 'auto': the signature is drawn automatically using a selected font\n     *  - 'load': the signature is loaded from an image file\n     * @param {boolean} [reset=false] - Set to true to reset the elements\n     *  even if the @see mode has not changed. By default nothing happens\n     *  if the @see mode is already selected.\n     */\n    setMode(mode, reset) {\n        if (reset !== true && mode === this.signMode) {\n            // prevent flickering and unnecessary compute\n            return;\n        }\n\n        this.state.signMode = mode;\n        this.signaturePad[this.state.signMode === \"draw\" ? \"on\" : \"off\"]();\n        this.clear();\n\n        if (this.state.signMode === \"auto\") {\n            // draw based on name\n            this.drawCurrentName();\n        }\n        this.props.onSignatureChange(this.state.signMode);\n    }\n\n    /**\n     * Returns whether the drawing area is currently empty.\n     *\n     * @returns {boolean} Whether the drawing area is currently empty.\n     */\n    get isSignatureEmpty() {\n        return this.signaturePad.isEmpty();\n    }\n\n    get loadIsInvalid() {\n        return this.state.signMode === \"load\" && this.state.loadIsInvalid;\n    }\n}\n", "import { Dialog } from \"@web/core/dialog/dialog\";\nimport { NameAndSignature } from \"./name_and_signature\";\n\nimport { Component, useState } from \"@odoo/owl\";\n\nexport class SignatureDialog extends Component {\n    static template = \"web.SignatureDialog\";\n    static components = { Dialog, NameAndSignature };\n    static props = {\n        defaultName: { type: String, optional: true },\n        nameAndSignatureProps: Object,\n        uploadSignature: Function,\n        close: Function,\n    };\n    static defaultProps = {\n        defaultName: \"\",\n    };\n\n    setup() {\n        this.signature = useState({\n            name: this.props.defaultName,\n            isSignatureEmpty: true,\n        });\n    }\n\n    /**\n     * Upload the signature image when confirm.\n     *\n     * @private\n     */\n    onClickConfirm() {\n        this.props.uploadSignature({\n            name: this.signature.name,\n            signatureImage: this.signature.getSignatureImage(),\n        });\n        this.props.close();\n    }\n\n    get nameAndSignatureProps() {\n        return {\n            ...this.props.nameAndSignatureProps,\n            signature: this.signature,\n        };\n    }\n}\n", "import { Component } from \"@odoo/owl\";\n\nexport class TagsList extends Component {\n    static template = \"web.TagsList\";\n    static defaultProps = {\n        displayText: true,\n    };\n    static props = {\n        displayText: { type: Boolean, optional: true },\n        visibleItemsLimit: { type: Number, optional: true },\n        tags: { type: Array, element: Object },\n    };\n\n    get visibleTagsCount() {\n        return this.props.visibleItemsLimit - 1;\n    }\n    get visibleTags() {\n        if (this.props.visibleItemsLimit && this.props.tags.length > this.props.visibleItemsLimit) {\n            return this.props.tags.slice(0, this.visibleTagsCount);\n        }\n        return this.props.tags;\n    }\n    get otherTags() {\n        if (this.props.visibleItemsLimit && this.props.tags.length > this.props.visibleItemsLimit) {\n            return this.props.tags.slice(this.visibleTagsCount);\n        }\n        return [];\n    }\n    get tooltipInfo() {\n        return JSON.stringify({\n            tags: this.otherTags.map((tag) => ({\n                text: tag.text,\n                id: tag.id,\n            })),\n        });\n    }\n}\n", "const RSTRIP_REGEXP = /(?=\\n[ \\t]*$)/;\n\nlet translationContext = null;\n\nconst TCTX = \"t-translation-context\";\nfunction getTranslationContext(node) {\n    if (node.hasAttribute(TCTX)) {\n        return node.getAttribute(TCTX);\n    }\n    return getTranslationContext(node.parentElement);\n}\n\nconst contextByTextNode = new Map();\nfunction setTranslationContext(node) {\n    switch (node.nodeType) {\n        case Node.TEXT_NODE:\n            if (node.nodeValue.trim() != \"\") {\n                contextByTextNode.set(node, translationContext);\n            }\n            break;\n        case Node.ELEMENT_NODE:\n            node.setAttribute(TCTX, translationContext);\n            break;\n    }\n}\n\nexport function applyContextToTextNode() {\n    for (const [textNode, context] of contextByTextNode) {\n        const wrapper = document.createElement(\"t\");\n        wrapper.setAttribute(TCTX, context);\n        textNode.before(wrapper);\n        wrapper.appendChild(textNode);\n    }\n    contextByTextNode.clear();\n}\n\nexport function deepClone(node) {\n    const clone = node.cloneNode();\n    if (node.nodeType === Node.TEXT_NODE) {\n        if (contextByTextNode.has(node)) {\n            contextByTextNode.set(clone, contextByTextNode.get(node));\n        }\n    }\n    if (node.childNodes?.length) {\n        for (const childNode of [...node.childNodes]) {\n            clone.append(deepClone(childNode));\n        }\n    }\n    return clone;\n}\n\n/**\n * The child nodes of operation represent new content to create before target or\n * or other elements to move before target from the target tree (tree from which target is part of).\n * Some processing of text nodes has to be done in order to normalize the situation.\n * Note: we assume that target has a parent element.\n * @param {Element} target\n * @param {Element} operation\n */\nfunction addBefore(target, operation) {\n    const nodes = getNodes(target, operation);\n    if (nodes.length === 0) {\n        return;\n    }\n    const { previousSibling } = target;\n    target.before(...nodes);\n    if (previousSibling?.nodeType === Node.TEXT_NODE) {\n        const [text1, text2] = previousSibling.data.split(RSTRIP_REGEXP);\n        previousSibling.data = text1.trimEnd();\n        if (text2 && nodes.some((n) => n.nodeType !== Node.TEXT_NODE)) {\n            const textNode = document.createTextNode(text2);\n            target.before(textNode);\n            if (textNode.previousSibling.nodeType === Node.TEXT_NODE) {\n                textNode.previousSibling.data = textNode.previousSibling.data.trimEnd();\n            }\n        }\n    }\n}\n\n/**\n * element is part of a tree. Here we return the root element of that tree.\n * Note: this root element is not necessarily the documentElement of the ownerDocument\n * of element (hence the following code).\n * @param {Element} element\n * @returns {Element}\n */\nfunction getRoot(element) {\n    while (element.parentElement) {\n        element = element.parentElement;\n    }\n    return element;\n}\n\nconst HASCLASS_REGEXP = /hasclass\\(([^)]*)\\)/g;\nconst CLASS_CONTAINS_REGEX = /contains\\(@class.*\\)/g;\n/**\n * @param {Element} operation\n * @returns {string}\n */\nfunction getXpath(operation) {\n    const xpath = operation.getAttribute(\"expr\");\n    if (odoo.debug) {\n        if (CLASS_CONTAINS_REGEX.test(xpath)) {\n            const parent = operation.closest(\"t[t-inherit]\");\n            const templateName = parent.getAttribute(\"t-name\") || parent.getAttribute(\"t-inherit\");\n            console.warn(\n                `Error-prone use of @class in template \"${templateName}\" (or one of its inheritors).` +\n                    \" Use the hasclass(*classes) function to filter elements by their classes\"\n            );\n        }\n    }\n    // hasclass does not exist in XPath 1.0 but is a custom function defined server side (see _hasclass) usable in lxml.\n    // Here we have to replace it by a complex condition (which is not nice).\n    // Note: we assume that classes do not contain the 2 chars , and )\n    return xpath.replaceAll(HASCLASS_REGEXP, (_, capturedGroup) =>\n        capturedGroup\n            .split(\",\")\n            .map((c) => `contains(concat(' ', @class, ' '), ' ${c.trim().slice(1, -1)} ')`)\n            .join(\" and \")\n    );\n}\n\n/**\n * @param {Element} element\n * @param {Element} operation\n * @returns {Node|null}\n */\nfunction getNode(element, operation) {\n    const root = getRoot(element);\n    const doc = new Document();\n    doc.appendChild(root); // => root is the documentElement of its ownerDocument (we do that in case root is a clone)\n    if (operation.tagName === \"xpath\") {\n        const xpath = getXpath(operation);\n        const result = doc.evaluate(xpath, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE);\n        return result.singleNodeValue;\n    }\n    const attributes = [...operation.attributes].filter((attr) => !attr.name.startsWith(TCTX));\n    for (const elem of root.querySelectorAll(operation.tagName)) {\n        if (\n            attributes.every(\n                ({ name, value }) => name === \"position\" || elem.getAttribute(name) === value\n            )\n        ) {\n            return elem;\n        }\n    }\n    return null;\n}\n\n/**\n * @param {Element} element\n * @param {Element} operation\n * @returns {Element}\n */\nfunction getElement(element, operation) {\n    const node = getNode(element, operation);\n    if (!node) {\n        throw new Error(`Element '${operation.outerHTML}' cannot be located in element tree`);\n    }\n    if (!(node instanceof Element)) {\n        throw new Error(`Found node ${node} instead of an element`);\n    }\n    return node;\n}\n\n/**\n * @param {Element} element\n * @param {Element} operation\n * @returns {Node[]}\n */\nfunction getNodes(element, operation) {\n    const nodes = [];\n    for (const childNode of operation.childNodes) {\n        if (childNode.tagName === \"xpath\" && childNode.getAttribute?.(\"position\") === \"move\") {\n            const node = getElement(element, childNode);\n            node.setAttribute(TCTX, getTranslationContext(node));\n            removeNode(node);\n            nodes.push(node);\n        } else {\n            setTranslationContext(childNode);\n            nodes.push(childNode);\n        }\n    }\n    return nodes;\n}\n\nfunction splitAndTrim(str, separator) {\n    return str.split(separator).map((s) => s.trim());\n}\n\n/**\n * @param {Element} target\n * @param {Element} operation\n */\nfunction modifyAttributes(target, operation) {\n    for (const child of operation.children) {\n        if (child.tagName !== \"attribute\") {\n            continue;\n        }\n        const attributeName = child.getAttribute(\"name\");\n        const firstNode = child.childNodes[0];\n        let value = firstNode?.nodeType === Node.TEXT_NODE ? firstNode.data : \"\";\n\n        const add = child.getAttribute(\"add\") || \"\";\n        const remove = child.getAttribute(\"remove\") || \"\";\n        if (add || remove) {\n            if (firstNode?.nodeType === Node.TEXT_NODE) {\n                throw new Error(`Useless element content ${firstNode.outerHTML}`);\n            }\n            const separator = child.getAttribute(\"separator\") || \",\";\n            const toRemove = new Set(splitAndTrim(remove, separator));\n            const values = splitAndTrim(target.getAttribute(attributeName) || \"\", separator).filter(\n                (s) => !toRemove.has(s)\n            );\n            values.push(...splitAndTrim(add, separator).filter((s) => s));\n            value = values.join(separator);\n        }\n\n        if (value) {\n            target.setAttribute(attributeName, value);\n            if (!(add || remove)) {\n                target.setAttribute(`t-translation-context-${attributeName}`, translationContext);\n            }\n        } else {\n            target.removeAttribute(attributeName);\n        }\n    }\n}\n\n/**\n * Remove node and normalize surrounind text nodes (if any)\n * Note: we assume that node has a parent element\n * @param {Node} node\n */\nfunction removeNode(node) {\n    const { nextSibling, previousSibling } = node;\n    node.remove();\n    if (\n        nextSibling?.nodeType === Node.TEXT_NODE &&\n        previousSibling?.nodeType === Node.TEXT_NODE &&\n        previousSibling.parentElement.firstChild === previousSibling\n    ) {\n        previousSibling.data = previousSibling.data.trimEnd();\n    }\n}\n\n/**\n * @param {Element} root\n * @param {Element} target\n * @param {Element} operation\n */\nfunction replace(root, target, operation) {\n    const mode = operation.getAttribute(\"mode\") || \"outer\";\n    switch (mode) {\n        case \"outer\": {\n            const result = operation.ownerDocument.evaluate(\n                \".//*[text()='$0']\",\n                operation,\n                null,\n                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE\n            );\n            target.setAttribute(TCTX, getTranslationContext(target));\n            for (let i = 0; i < result.snapshotLength; i++) {\n                const loc = result.snapshotItem(i);\n                loc.firstChild.replaceWith(deepClone(target));\n            }\n            if (target.parentElement) {\n                const nodes = getNodes(target, operation);\n                target.replaceWith(...nodes);\n            } else {\n                let operationContent = null;\n                let comment = null;\n                for (const child of operation.childNodes) {\n                    if (child.nodeType === Node.ELEMENT_NODE) {\n                        setTranslationContext(child);\n                        operationContent = child;\n                        break;\n                    }\n                    if (child.nodeType === Node.COMMENT_NODE) {\n                        comment = child;\n                    }\n                }\n                root = deepClone(operationContent);\n                if (target.hasAttribute(\"t-name\")) {\n                    root.setAttribute(\"t-name\", target.getAttribute(\"t-name\"));\n                }\n                if (comment) {\n                    root.prepend(comment);\n                }\n            }\n            break;\n        }\n        case \"inner\":\n            while (target.firstChild) {\n                target.removeChild(target.lastChild);\n            }\n            for (const node of [...operation.childNodes]) {\n                setTranslationContext(node);\n                target.append(node);\n            }\n            break;\n        default:\n            throw new Error(`Invalid mode attribute: '${mode}'`);\n    }\n    return root;\n}\n\n/**\n * @param {Element} root\n * @param {Element} operations is a single element whose children represent operations to perform on root\n * @param {string} [url=\"\"]\n * @returns {Element} root modified (in place) by the operations\n */\nexport function applyInheritance(root, operations, url = \"\") {\n    translationContext = url.split(\"/\")[1] ?? \"\"; // use addon name as context\n    for (const operation of operations.children) {\n        const target = getElement(root, operation);\n        const position = operation.getAttribute(\"position\") || \"inside\";\n\n        if (odoo.debug && url) {\n            const attributes = [...operation.attributes].map(\n                ({ name, value }) =>\n                    `${name}=${JSON.stringify(name === \"position\" ? position : value)}`\n            );\n            const comment = document.createComment(\n                ` From file: ${url} ; ${attributes.join(\" ; \")} `\n            );\n            if (position === \"attributes\") {\n                target.before(comment); // comment won't be visible if target is root\n            } else {\n                operation.prepend(comment);\n            }\n        }\n\n        switch (position) {\n            case \"replace\": {\n                root = replace(root, target, operation); // root can be replaced (see outer mode)\n                break;\n            }\n            case \"attributes\": {\n                modifyAttributes(target, operation);\n                break;\n            }\n            case \"inside\": {\n                const sentinel = document.createElement(\"sentinel\");\n                target.append(sentinel);\n                addBefore(sentinel, operation);\n                removeNode(sentinel);\n                break;\n            }\n            case \"after\": {\n                const sentinel = document.createElement(\"sentinel\");\n                target.after(sentinel);\n                addBefore(sentinel, operation);\n                removeNode(sentinel);\n                break;\n            }\n            case \"before\": {\n                addBefore(target, operation);\n                break;\n            }\n            default:\n                throw new Error(`Invalid position attribute: '${position}'`);\n        }\n    }\n    translationContext = null;\n    return root;\n}\n", "import {\n    applyContextToTextNode,\n    applyInheritance,\n    deepClone,\n} from \"@web/core/template_inheritance\";\n\nfunction getClone(template) {\n    const c = deepClone(template);\n    new Document().append(c); // => c is the documentElement of its ownerDocument\n    return c;\n}\n\nfunction getParsedTemplate(templateString) {\n    const doc = parser.parseFromString(templateString, \"text/xml\");\n    for (const processor of templateProcessors) {\n        processor(doc);\n    }\n    return doc.firstChild;\n}\n\nfunction _getTemplate(name, blockId = null) {\n    if (!(name in parsedTemplates)) {\n        if (!(name in templates)) {\n            return null;\n        }\n        const templateString = templates[name];\n        parsedTemplates[name] = getParsedTemplate(templateString);\n        const inheritFrom = parsedTemplates[name].getAttribute(\"t-inherit\");\n        if (!inheritFrom) {\n            const addon = info[name].url.split(\"/\")[1];\n            parsedTemplates[name].setAttribute(\"t-translation-context\", addon);\n        }\n    }\n    let processedTemplate = parsedTemplates[name];\n\n    const inheritFrom = processedTemplate.getAttribute(\"t-inherit\");\n    if (inheritFrom) {\n        const parentTemplate = _getTemplate(inheritFrom, blockId || info[name].blockId);\n        if (!parentTemplate) {\n            throw new Error(\n                `Constructing template ${name}: template parent ${inheritFrom} not found`\n            );\n        }\n        const element = getClone(processedTemplate);\n        processedTemplate = applyInheritance(getClone(parentTemplate), element, info[name].url);\n        if (processedTemplate.tagName !== element.tagName) {\n            const temp = processedTemplate;\n            processedTemplate = new Document().createElement(element.tagName);\n            processedTemplate.append(...temp.childNodes);\n        }\n        for (const { name, value } of element.attributes) {\n            if (![\"t-inherit\", \"t-inherit-mode\"].includes(name)) {\n                processedTemplate.setAttribute(name, value);\n            }\n        }\n    }\n\n    let cloned = false;\n    for (const otherBlockId in templateExtensions[name] || {}) {\n        if (blockId && otherBlockId > blockId) {\n            break;\n        }\n        if (!(name in parsedTemplateExtensions)) {\n            parsedTemplateExtensions[name] = {};\n        }\n        if (!(otherBlockId in parsedTemplateExtensions[name])) {\n            parsedTemplateExtensions[name][otherBlockId] = [];\n            for (const { templateString, url } of templateExtensions[name][otherBlockId]) {\n                parsedTemplateExtensions[name][otherBlockId].push({\n                    template: getParsedTemplate(templateString),\n                    url,\n                });\n            }\n        }\n        for (const { template, url } of parsedTemplateExtensions[name][otherBlockId]) {\n            if (!urlFilters.every((filter) => filter(url))) {\n                continue;\n            }\n            if (!inheritFrom && !cloned) {\n                cloned = true;\n                processedTemplate = getClone(processedTemplate);\n            }\n            processedTemplate = applyInheritance(processedTemplate, getClone(template), url);\n        }\n    }\n\n    return processedTemplate;\n}\n\nfunction isRegistered(...args) {\n    const key = JSON.stringify([...args]);\n    if (registered.has(key)) {\n        return true;\n    }\n    registered.add(key);\n    return false;\n}\n\nconst info = Object.create(null);\nconst parsedTemplateExtensions = Object.create(null);\nconst parsedTemplates = Object.create(null);\nconst parser = new DOMParser();\n/** @type {Map<string, Element>} */\nconst processedTemplates = new Map();\nconst registered = new Set();\n/** @type {Record<string, Record<number, ({ templateString: string, url: string })[]>>} */\nconst templateExtensions = Object.create(null);\n/** @type {((document: Document) => void)[]} */\nconst templateProcessors = [];\n/** @type {Record<string, string>} */\nconst templates = Object.create(null);\nlet blockType = null;\nlet blockId = 0;\n/** @type {((url: string) => boolean)[]} */\nlet urlFilters = [];\n\nexport function checkPrimaryTemplateParents(namesToCheck) {\n    const missing = new Set(namesToCheck.filter((name) => !(name in templates)));\n    if (missing.size) {\n        console.error(`Missing (primary) parent templates: ${[...missing].join(\", \")}`);\n    }\n}\n\nexport function clearProcessedTemplates() {\n    processedTemplates.clear();\n}\n\n/**\n * @param {string} name\n */\nexport function getTemplate(name) {\n    if (!processedTemplates.has(name)) {\n        processedTemplates.set(name, _getTemplate(name));\n        applyContextToTextNode();\n    }\n    return processedTemplates.get(name);\n}\n\nexport function registerTemplate(name, url, templateString) {\n    if (isRegistered(...arguments)) {\n        return;\n    }\n    if (blockType !== \"templates\") {\n        blockType = \"templates\";\n        blockId++;\n    }\n    if (name in templates && (info[name].url !== url || templates[name] !== templateString)) {\n        throw new Error(`Template ${name} already exists`);\n    }\n    templates[name] = templateString;\n    info[name] = { blockId, url };\n\n    return () => {\n        delete templates[name];\n        delete info[name];\n        delete parsedTemplates[name];\n        delete parsedTemplateExtensions[name];\n        processedTemplates.delete(name);\n        registered.delete(JSON.stringify([...arguments]));\n    };\n}\n\nexport function registerTemplateExtension(inheritFrom, url, templateString) {\n    if (isRegistered(...arguments)) {\n        return;\n    }\n    if (blockType !== \"extensions\") {\n        blockType = \"extensions\";\n        blockId++;\n    }\n    if (!templateExtensions[inheritFrom]) {\n        templateExtensions[inheritFrom] = [];\n    }\n    if (!templateExtensions[inheritFrom][blockId]) {\n        templateExtensions[inheritFrom][blockId] = [];\n    }\n    templateExtensions[inheritFrom][blockId].push({\n        templateString,\n        url,\n    });\n\n    return () => {\n        const index = templateExtensions[inheritFrom]?.[blockId]?.findIndex(\n            (ext) => ext.templateString === templateString && ext.url === url\n        );\n        if (Number.isInteger(index) && index > -1) {\n            templateExtensions[inheritFrom][blockId].splice(index, 1);\n        }\n        registered.delete(JSON.stringify([...arguments]));\n    };\n}\n\n/**\n * @param {(document: Document) => void} processor\n */\nexport function registerTemplateProcessor(processor) {\n    templateProcessors.push(processor);\n}\n\n/**\n * @param {typeof urlFilters} filters\n */\nexport function setUrlFilters(filters) {\n    const prev = urlFilters;\n    urlFilters = filters;\n    return () => {\n        urlFilters = prev;\n    };\n}\n", "import { Component, onWillUpdateProps, useRef, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { useDropdownState } from \"@web/core/dropdown/dropdown_hooks\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\nimport { Time, parseTime } from \"@web/core/l10n/time\";\nimport { mergeClasses } from \"@web/core/utils/classname\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nconst HOURS = [...Array(24)].map((_, i) => i);\nconst MINUTES = [...Array(60)].map((_, i) => i);\n\n/**\n * @typedef TimePickerProps\n * @property {string} [class=\"\"]\n * @property {string|Time} [value]\n * @property {(value: Time) => any} [onChange]\n * @property {() => {}} [onInvalid]\n * @property {boolean} [showSeconds=false]\n * @property {number} [minutesRounding=5]\n */\n\nexport class TimePicker extends Component {\n    static template = \"web.TimePicker\";\n    static components = {\n        Dropdown,\n        DropdownItem,\n    };\n    static props = {\n        cssClass: { type: [String, Array, Object], optional: true },\n        inputCssClass: { type: [String, Array, Object], optional: true },\n        value: { type: [String, Time, { value: false }, { value: null }], optional: true },\n        onChange: { type: Function, optional: true },\n        onInvalid: { type: Function, optional: true },\n        showSeconds: { type: Boolean, optional: true },\n        minutesRounding: { type: Number, optional: true },\n        placeholder: { type: String, optional: true },\n    };\n    static defaultProps = {\n        cssClass: {},\n        inputCssClass: {},\n        value: \"00:00\",\n        onChange: () => {},\n        onInvalid: () => {},\n        showSeconds: false,\n        minutesRounding: 5,\n    };\n\n    setup() {\n        this.inputRef = useRef(\"inputRef\");\n        this.menuRef = useChildRef();\n        this.dropdownState = useDropdownState();\n\n        this.state = useState({\n            value: null,\n            inputValue: \"\",\n            isValid: true,\n        });\n\n        /**@type {Time[]}*/\n        this.suggestions = [];\n        this.isNavigating = false;\n        this.navigationOptions = this.getNavigationOptions();\n        this.onPropsUpdated(this.props);\n\n        onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps));\n    }\n\n    get cssClass() {\n        return mergeClasses(this.props.cssClass, {\n            o_time_picker_seconds: this.props.showSeconds,\n        });\n    }\n\n    get inputCssClass() {\n        return mergeClasses(this.props.inputCssClass, {\n            o_invalid: !this.state.isValid,\n        });\n    }\n\n    /**\n     * @returns {import(\"@web/core/navigation/navigation\").NavigationOptions}\n     */\n    getNavigationOptions() {\n        const handleArrow = (navigator) => {\n            const value = this.suggestions[navigator.activeItemIndex];\n            if (value) {\n                this.state.inputValue = value.toString(this.props.showSeconds);\n            }\n        };\n\n        return {\n            virtualFocus: true,\n            onUpdated: (navigator) => (this.navigator = navigator),\n            hotkeys: {\n                enter: {\n                    bypassEditableProtection: true,\n                    callback: (navigator) => {\n                        if (!this.isNavigating) {\n                            const value = parseTime(this.inputRef.el.value, this.props.showSeconds);\n                            if (value) {\n                                this.setValue(value);\n                                this.close();\n                            }\n                        } else if (navigator.activeItem) {\n                            navigator.activeItem.select();\n                        }\n                    },\n                },\n                tab: {\n                    bypassEditableProtection: true,\n                    callback: (navigator) => {\n                        if (navigator.activeItemIndex >= 0) {\n                            this.setValue(this.suggestions[navigator.activeItemIndex]);\n                            this.close();\n                        }\n                    },\n                },\n                arrowdown: {\n                    callback: (navigator) => {\n                        navigator.next();\n                        handleArrow(navigator);\n                    },\n                },\n                arrowup: {\n                    callback: (navigator) => {\n                        navigator.previous();\n                        handleArrow(navigator);\n                    },\n                },\n            },\n        };\n    }\n\n    /**\n     * @param {TimePickerProps} props\n     */\n    onPropsUpdated(props) {\n        if (this.suggestions.length === 0) {\n            this.suggestions = this.getSuggestions(props);\n        }\n\n        this.updateStateValue(Time.from(props.value));\n    }\n\n    /**\n     * @param {TimePickerProps} props\n     * @returns {Time[]}\n     */\n    getSuggestions(props) {\n        const suggestions = [];\n        const rounding = props.minutesRounding <= 5 ? 15 : props.minutesRounding;\n        const minutes = MINUTES.filter((m) => !(m % rounding));\n        for (const hour of HOURS) {\n            for (const minute of minutes) {\n                suggestions.push(new Time({ hour, minute }));\n            }\n        }\n        return suggestions;\n    }\n\n    /**\n     * @param {Time|null} newValue\n     * @param {boolean} [cleanValue=true]\n     */\n    setValue(newValue, cleanValue = true) {\n        if (newValue && cleanValue) {\n            if (this.props.minutesRounding > 1) {\n                newValue.roundMinutes(this.props.minutesRounding);\n            }\n            // If showSeconds is false, keep the seconds from\n            // the original props.value\n            if (!this.props.showSeconds && this.state.value) {\n                newValue.second = this.state.value.second;\n            }\n        }\n\n        const lastValue = this.lastValue;\n        this.updateStateValue(newValue);\n        if (newValue && !newValue.equals(lastValue, this.props.showSeconds)) {\n            this.props.onChange(newValue.copy());\n        }\n    }\n\n    /**\n     * @param {Time|null} newValue\n     */\n    updateStateValue(newValue) {\n        if (\n            newValue === this.lastValue ||\n            newValue?.equals(this.lastValue, this.props.showSeconds)\n        ) {\n            return;\n        }\n\n        this.lastValue = newValue?.copy() ?? newValue;\n        this.state.value = newValue;\n        this.state.inputValue = newValue ? newValue.toString(this.props.showSeconds) : \"\";\n        this.state.isValid = true;\n    }\n\n    /**\n     * @param {Time} value\n     */\n    onItemSelected(value) {\n        this.setValue(value);\n        this.close();\n    }\n\n    /**\n     * @param {InputEvent} event\n     */\n    onInput(event) {\n        this.ensureOpen();\n\n        const value = parseTime(this.inputRef.el.value, this.props.showSeconds);\n        this.state.isValid = value !== null;\n\n        if (!this.navigator) {\n            return;\n        }\n\n        let index = -1;\n        if (this.state.isValid) {\n            index = this.suggestions.findIndex((s) => s.equals(value));\n        }\n\n        if (index === -1) {\n            this.navigator.activeItem?.setInactive();\n        } else {\n            this.navigator.items[index]?.setActive();\n        }\n    }\n\n    onChange() {\n        const value = parseTime(this.inputRef.el.value, this.props.showSeconds);\n        this.state.isValid = value !== null;\n        if (this.state.isValid) {\n            this.setValue(value);\n            this.close();\n        } else {\n            this.props.onInvalid();\n        }\n    }\n\n    /**\n     * @param {KeyboardEvent} event\n     */\n    onKeydown(event) {\n        this.isNavigating = [\"arrowup\", \"arrowdown\"].includes(getActiveHotkey(event));\n    }\n\n    ensureOpen() {\n        if (!this.dropdownState.isOpen) {\n            this.isNavigating = false;\n            this.dropdownState.open();\n            this.inputRef.el.select();\n        }\n    }\n\n    close() {\n        this.dropdownState.close();\n    }\n\n    /**\n     * @returns {string}\n     */\n    getPlaceholder() {\n        if (typeof this.props.placeholder === \"string\") {\n            return this.props.placeholder;\n        }\n        const seconds = this.props.showSeconds ? \":ss\" : \"\";\n        return `hh:mm${seconds}`;\n    }\n\n    onDropdownOpened() {\n        if (this.navigator) {\n            const index = this.state.value\n                ? this.suggestions.findIndex((s) =>\n                      s.equals(this.state.value, this.props.showSeconds)\n                  )\n                : 0;\n            this.navigator.items[index]?.setActive();\n        }\n    }\n}\n", "import { Component } from \"@odoo/owl\";\n\nexport class Tooltip extends Component {\n    static template = \"web.Tooltip\";\n    static props = {\n        close: Function,\n        tooltip: { type: String, optional: true },\n        template: { type: String, optional: true },\n        info: { optional: true },\n    };\n}\n", "import { useService } from \"@web/core/utils/hooks\";\n\nimport { useEffect, useRef } from \"@odoo/owl\";\n\nexport function useTooltip(refName, params) {\n    const tooltip = useService(\"tooltip\");\n    const ref = useRef(refName);\n    useEffect(\n        (el) => tooltip.add(el, params),\n        () => [ref.el]\n    );\n}\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { Tooltip } from \"./tooltip\";\nimport { hasTouch } from \"@web/core/browser/feature_detection\";\n\nimport { whenReady } from \"@odoo/owl\";\n\n/**\n * The tooltip service allows to display custom tooltips on every elements with\n * a \"data-tooltip\" attribute. This attribute can be set on elements for which\n * we prefer a custom tooltip instead of the native one displaying the value of\n * the \"title\" attribute.\n *\n * Usage:\n *   <button data-tooltip=\"This is a tooltip\">Do something</button>\n *\n * The ideal position of the tooltip can be specified thanks to the attribute\n * \"data-tooltip-position\":\n *   <button data-tooltip=\"This is a tooltip\" data-tooltip-position=\"left\">Do something</button>\n *\n * The opening delay can be modified with the \"data-tooltip-delay\" attribute (default: 400):\n *   <button data-tooltip=\"This is a tooltip\" data-tooltip-delay=\"0\">Do something</button>\n *\n * The default behaviour on touch devices to open the tooltip can be modified from \"hold-to-show\"\n * to \"tap-to-show\" \"with the data-tooltip-touch-tap-to-show\" attribute:\n *  <button data-tooltip=\"This is a tooltip\" data-tooltip-touch-tap-to-show=\"true\">Do something</button>\n *\n * For advanced tooltips containing dynamic and/or html content, the\n * \"data-tooltip-template\" and \"data-tooltip-info\" attributes can be used.\n * For example, let's suppose the following qweb template:\n *   <t t-name=\"some_template\">\n *     <ul>\n *       <li>info.x</li>\n *       <li>info.y</li>\n *     </ul>\n *   </t>\n * This template can then be used in a tooltip as follows:\n *   <button data-tooltip-template=\"some_template\" data-tooltip-info=\"info\">Do something</button>\n * with \"info\" being a stringified object with two keys \"x\" and \"y\".\n */\n\nexport const OPEN_DELAY = 400;\nexport const CLOSE_DELAY = 200;\nexport const SHOW_AFTER_DELAY = 250;\n\nexport const tooltipService = {\n    dependencies: [\"popover\"],\n    start(env, { popover }) {\n        let openTooltipTimeout;\n        let closeTooltip;\n        let showTimer;\n        let target = null;\n        const elementsWithTooltips = new WeakMap();\n\n        /**\n         * Detect if the current node is the `sup` tooltip node\n         * @param {HTMLElement} el\n         * @return {boolean}\n         */\n        function isHelpNode(el) {\n            return (\n                el.textContent === \"?\" &&\n                (el.hasAttribute(\"data-tooltip\") || el.hasAttribute(\"data-tooltip-template\"))\n            );\n        }\n\n        /**\n         * Closes the currently opened tooltip if any, or prevent it from opening.\n         */\n        function cleanup() {\n            target = null;\n            browser.clearTimeout(openTooltipTimeout);\n            openTooltipTimeout = null;\n            if (closeTooltip) {\n                closeTooltip();\n                closeTooltip = null;\n            }\n        }\n\n        /**\n         * Checks that the target is in the DOM and we're hovering the target.\n         * @returns {boolean}\n         */\n        function shouldCleanup() {\n            if (!target) {\n                return false;\n            }\n            if (!document.body.contains(target)) {\n                return true; // target is no longer in the DOM\n            }\n            return false;\n        }\n\n        /**\n         * Checks whether there is a tooltip registered on the event target, and\n         * if there is, creates a timeout to open the corresponding tooltip\n         * after a delay.\n         *\n         * @param {HTMLElement} el the element on which to add the tooltip\n         * @param {object} param1\n         * @param {string} [param1.tooltip] the string to add as a tooltip, if\n         *  no tooltip template is specified\n         * @param {string} [param1.template] the name of the template to use for\n         *  tooltip, if any\n         * @param {object} [param1.info] info for the tooltip template\n         * @param {'top'|'bottom'|'left'|'right'} param1.position\n         * @param {number} [param1.delay] delay after which the popover should\n         *  open\n         */\n        function openTooltip(el, { tooltip = \"\", template, info, position, delay = OPEN_DELAY }) {\n            cleanup();\n            if (!tooltip && !template) {\n                return;\n            }\n\n            target = el;\n            // Prevent title from showing on a parent at the same time\n            target.title = \"\";\n            const timeoutDelay = isHelpNode(el) ? 0 : delay;\n            openTooltipTimeout = browser.setTimeout(() => {\n                // verify that the element is still in the DOM\n                if (target.isConnected) {\n                    closeTooltip = popover.add(\n                        target,\n                        Tooltip,\n                        { tooltip, template, info },\n                        { position }\n                    );\n                }\n            }, timeoutDelay);\n        }\n\n        /**\n         * Checks whether there is a tooltip registered on the element, and\n         * if there is, creates a timeout to open the corresponding tooltip\n         * after a delay.\n         *\n         * @param {HTMLElement} el\n         */\n        function openElementsTooltip(el) {\n            // Fix weird behavior in Firefox where MouseEvent can be dispatched\n            // from TEXT_NODE, even if they shouldn't...\n            if (el.nodeType === Node.TEXT_NODE) {\n                return;\n            }\n            const element = el.closest(\"[data-tooltip], [data-tooltip-template]\");\n            if (elementsWithTooltips.has(el)) {\n                openTooltip(el, elementsWithTooltips.get(el));\n            } else if (element) {\n                const dataset = element.dataset;\n                const params = {\n                    tooltip: dataset.tooltip,\n                    template: dataset.tooltipTemplate,\n                    position: dataset.tooltipPosition,\n                };\n                if (dataset.tooltipInfo) {\n                    params.info = JSON.parse(dataset.tooltipInfo);\n                }\n                if (dataset.tooltipDelay) {\n                    params.delay = parseInt(dataset.tooltipDelay, 10);\n                }\n                openTooltip(element, params);\n            }\n        }\n\n        /**\n         * Checks whether there is a tooltip registered on the event target, and\n         * if there is, creates a timeout to open the corresponding tooltip\n         * after a delay.\n         *\n         * @param {MouseEvent} ev a \"mouseenter\" event\n         */\n        function onMouseenter(ev) {\n            openElementsTooltip(ev.target);\n        }\n\n        /**\n         * Check whether there is a tooltip registered on the event target, and if there is,\n         * cleanup it.\n         * @param {MouseEvent} ev a \"click\" event\n         */\n        function onClick(ev) {\n            if (isHelpNode(ev.target)) {\n                ev.preventDefault();\n            }\n            cleanupTooltip(ev);\n        }\n\n        function cleanupTooltip(ev) {\n            if (target === ev.target.closest(\"[data-tooltip], [data-tooltip-template]\")) {\n                cleanup();\n            }\n        }\n        /**\n         * Checks whether there is a tooltip registered on the event target, and\n         * if there is, creates a timeout to open the corresponding tooltip\n         * after a delay.\n         *\n         * @param {TouchEvent} ev a \"touchstart\" event\n         */\n        function onTouchStart(ev) {\n            cleanup();\n            const timeoutDelay = isHelpNode(ev.target) ? 0 : SHOW_AFTER_DELAY;\n            showTimer = browser.setTimeout(() => {\n                openElementsTooltip(ev.target);\n            }, timeoutDelay);\n        }\n\n        whenReady(() => {\n            // Regularly check that the target is still in the DOM and if not, close the tooltip\n            browser.setInterval(() => {\n                if (shouldCleanup()) {\n                    cleanup();\n                }\n            }, CLOSE_DELAY);\n\n            if (hasTouch()) {\n                document.body.addEventListener(\"touchstart\", onTouchStart);\n\n                document.body.addEventListener(\"touchend\", (ev) => {\n                    if (isHelpNode(ev.target)) {\n                        ev.preventDefault();\n                        return;\n                    }\n                    if (ev.target.closest(\"[data-tooltip], [data-tooltip-template]\")) {\n                        if (!ev.target.dataset.tooltipTouchTapToShow) {\n                            browser.clearTimeout(showTimer);\n                            browser.clearTimeout(openTooltipTimeout);\n                        }\n                    }\n                });\n                document.body.addEventListener(\"touchcancel\", (ev) => {\n                    if (isHelpNode(ev.target)) {\n                        ev.preventDefault();\n                        return;\n                    }\n                    if (ev.target.closest(\"[data-tooltip], [data-tooltip-template]\")) {\n                        if (!ev.target.dataset.tooltipTouchTapToShow) {\n                            browser.clearTimeout(showTimer);\n                            browser.clearTimeout(openTooltipTimeout);\n                        }\n                    }\n                });\n            }\n\n            // Listen (using event delegation) to \"mouseenter\" events to open the tooltip if any\n            document.body.addEventListener(\"mouseenter\", onMouseenter, { capture: true });\n            // Listen (using event delegation) to \"mouseleave\" events to close the tooltip if any\n            document.body.addEventListener(\"mouseleave\", cleanupTooltip, { capture: true });\n            document.body.addEventListener(\"click\", onClick, { capture: true });\n        });\n\n        return {\n            add(el, params) {\n                elementsWithTooltips.set(el, params);\n                return () => {\n                    elementsWithTooltips.delete(el);\n                    if (target === el) {\n                        cleanup();\n                    }\n                };\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"tooltip\", tooltipService);\n", "import { browser } from \"./browser/browser\";\n\nimport {\n    Component,\n    onWillUpdateProps,\n    status,\n    useComponent,\n    useEffect,\n    useState,\n    xml,\n} from \"@odoo/owl\";\n\n// Allows to disable transitions globally, useful for testing (and maybe for\n// a reduced motion setting in the future?)\nexport const config = {\n    disabled: false,\n};\n/**\n * Creates a transition to be used within the current component. Usage:\n *  --- in JS:\n *  this.transition = useTransition({ name: \"myClass\" });\n *  --- in XML:\n *  <div t-if=\"transition.shouldMount\" t-att-class=\"transition.class\"/>\n *\n * @param {Object} options\n * @param {string} options.name the prefix to use for the transition classes\n * @param {boolean} [options.initialVisibility=true] whether to start the\n *  transition in the on or off state\n * @param {number} [options.immediate=false] (only relevant when initialVisibility\n *  is true) set to true to animate initially. By default, there's no animation\n *  if the element is initially visible.\n * @param {number} [options.leaveDuration] the leaveDuration of the transition\n * @param {Function} [options.onLeave] a function that will be called when the\n *  element will be removed in the next render cycle\n * @returns {{ shouldMount, class }} an object containing two fields that\n *  indicate whether an element on which the transition is applied should be\n *  mounted and the class string that should be put on it\n */\nexport function useTransition({\n    name,\n    initialVisibility = true,\n    immediate = false,\n    leaveDuration = 500,\n    onLeave = () => {},\n}) {\n    const component = useComponent();\n    const state = useState({\n        shouldMount: initialVisibility,\n        stage: initialVisibility ? \"enter\" : \"leave\",\n    });\n\n    if (config.disabled) {\n        return {\n            get shouldMount() {\n                return state.shouldMount;\n            },\n            set shouldMount(val) {\n                state.shouldMount = val;\n            },\n            get className() {\n                return `${name} ${name}-enter-active`;\n            },\n            get stage() {\n                return \"enter-active\";\n            },\n        };\n    }\n    // We need to allow the element to be mounted in the enter state so that it\n    // will get the transition when we activate the enter-active class. This\n    // onNextPatch allows us to activate the class that we want the next time\n    // the component is patched.\n    let onNextPatch = null;\n    useEffect(() => {\n        if (onNextPatch) {\n            onNextPatch();\n            onNextPatch = null;\n        }\n    });\n\n    let prevState, timer;\n    const transition = {\n        get shouldMount() {\n            return state.shouldMount;\n        },\n        set shouldMount(newState) {\n            if (newState === prevState) {\n                return;\n            }\n            browser.clearTimeout(timer);\n            prevState = newState;\n            // when true - transition from enter to enter-active\n            // when false - transition from enter-active to leave, unmount after leaveDuration\n            if (newState) {\n                if (status(component) === \"mounted\" || immediate) {\n                    state.stage = \"enter\";\n                    // force a render here so that we get a patch even if the state didn't change\n                    component.render();\n                    onNextPatch = () => {\n                        state.stage = \"enter-active\";\n                    };\n                } else {\n                    state.stage = \"enter-active\";\n                }\n                state.shouldMount = true;\n            } else {\n                state.stage = \"leave\";\n                timer = browser.setTimeout(() => {\n                    state.shouldMount = false;\n                    onLeave();\n                }, leaveDuration);\n            }\n        },\n        get className() {\n            return `${name} ${name}-${state.stage}`;\n        },\n        get stage() {\n            return state.stage;\n        },\n    };\n    transition.shouldMount = initialVisibility;\n    return transition;\n}\n\n/**\n * A higher order component that handles a transition to be used within its\n * default slot. Generally, the useTransition hook is simpler to use, but the\n * HOC has the advantage that it can be spawned as needed during the render (eg:\n * in a t-foreach loop) without knowing at setup-time how many transitions need\n * to be created. @see useTransition\n */\nexport class Transition extends Component {\n    static template = xml`<t t-slot=\"default\" t-if=\"transition.shouldMount\" className=\"transition.className\"/>`;\n    static props = {\n        name: String,\n        visible: { type: Boolean, optional: true },\n        immediate: { type: Boolean, optional: true },\n        leaveDuration: { type: Number, optional: true },\n        onLeave: { type: Function, optional: true },\n        slots: Object,\n    };\n\n    setup() {\n        const { immediate, visible, leaveDuration, name, onLeave } = this.props;\n        this.transition = useTransition({\n            initialVisibility: visible,\n            immediate,\n            leaveDuration,\n            name,\n            onLeave,\n        });\n        onWillUpdateProps(({ visible = true }) => {\n            this.transition.shouldMount = visible;\n        });\n    }\n}\n", "import { COMPARATORS, TERM_OPERATORS_NEGATION_EXTENDED } from \"./operators\";\n\nexport function isBool(ast) {\n    return ast.type === 8 && ast.fn.type === 5 && ast.fn.value === \"bool\" && ast.args.length === 1;\n}\n\nexport function isNot(ast) {\n    return ast.type === 6 && ast.op === \"not\";\n}\n\nexport function not(ast) {\n    if (isNot(ast)) {\n        return ast.right;\n    }\n    if (ast.type === 2) {\n        return { ...ast, value: !ast.value };\n    }\n    if (ast.type === 7 && COMPARATORS.includes(ast.op)) {\n        return { ...ast, op: TERM_OPERATORS_NEGATION_EXTENDED[ast.op] }; // do not use this if ast is within a domain context!\n    }\n    return { type: 6, op: \"not\", right: isBool(ast) ? ast.args[0] : ast };\n}\n\nexport function isValidPath(ast, options) {\n    const getFieldDef = options.getFieldDef || (() => null);\n    if (ast.type === 5) {\n        return getFieldDef(ast.value) !== null;\n    }\n    return false;\n}\n", "import { Domain } from \"@web/core/domain\";\nimport { formatAST, parseExpr } from \"@web/core/py_js/py\";\nimport { toPyValue } from \"@web/core/py_js/py_utils\";\n\n/** @typedef { import(\"@web/core/py_js/py_parser\").AST } AST */\n/** @typedef {import(\"@web/core/domain\").DomainRepr} DomainRepr */\n\n/**\n * @typedef {number|string|boolean|Expression} Atom\n */\n\n/**\n * @typedef {Atom|Atom[]} Value\n */\n\n/**\n * @typedef {Object} Condition\n * @property {\"condition\"} type\n * @property {Value} path\n * @property {Value} operator\n * @property {Value|Tree} value\n * @property {boolean} negate\n */\n\n/**\n * @typedef {Object} ComplexCondition\n * @property {\"complex_condition\"} type\n * @property {string} value expression\n */\n\n/**\n * @typedef {Object} Connector\n * @property {\"connector\"} type\n * @property {boolean} negate\n * @property {\"|\"|\"&\"} value\n * @property {Tree[]} children\n */\n\n/**\n * @typedef {Connector|Condition|ComplexCondition} Tree\n */\n\n/**\n * @typedef {Object} Options\n * @property {(value: Value) => (null|Object)} [getFieldDef]\n * @property {boolean} [distributeNot]\n */\n\nexport class Expression {\n    constructor(ast) {\n        if (typeof ast === \"string\") {\n            ast = parseExpr(ast);\n        }\n        this._ast = ast;\n        this._expr = formatAST(ast);\n    }\n\n    toAST() {\n        return this._ast;\n    }\n\n    toString() {\n        return this._expr;\n    }\n}\n\n/**\n * @param {string} expr\n * @returns {Expression}\n */\nexport function expression(expr) {\n    return new Expression(expr);\n}\n\n/**\n * @param {\"|\"|\"&\"} value\n * @param {Tree[]} [children=[]]\n * @param {boolean} [negate=false]\n * @returns {Connector}\n */\nexport function connector(value, children = [], negate = false) {\n    return { type: \"connector\", value, children, negate };\n}\n\n/**\n * @param {string} value\n * @returns {ComplexCondition}\n */\nexport function complexCondition(value) {\n    parseExpr(value);\n    return { type: \"complex_condition\", value };\n}\n\n/**\n * @param {Value} path\n * @param {Value} operator\n * @param {Value|Tree} value\n * @param {boolean} [negate=false]\n * @returns {Condition}\n */\nexport function condition(path, operator, value, negate = false) {\n    return { type: \"condition\", path, operator, value, negate };\n}\n\nexport const TRUE_TREE = condition(1, \"=\", 1);\nexport const FALSE_TREE = condition(0, \"=\", 1);\n\n/**\n * @param {Value} value\n * @returns {Value}\n */\nfunction cloneValue(value) {\n    if (value instanceof Expression) {\n        return new Expression(value.toAST());\n    }\n    if (Array.isArray(value)) {\n        return value.map(cloneValue);\n    }\n    return value;\n}\n\n/**\n * @param {Tree} tree\n * @returns {Tree}\n */\nexport function cloneTree(tree) {\n    const clone = {};\n    for (const key in tree) {\n        clone[key] = cloneValue(tree[key]);\n    }\n    return clone;\n}\n\nconst areEqualValues = (value, otherValue) => formatValue(value) === formatValue(otherValue);\n\nconst areEqualArraysOfTrees = (array, otherArray) => {\n    if (array.length !== otherArray.length) {\n        return false;\n    }\n    for (let i = 0; i < array.length; i++) {\n        const elem = array[i];\n        const otherElem = otherArray[i];\n        if (!areEqualTrees(elem, otherElem)) {\n            return false;\n        }\n    }\n    return true;\n};\n\nexport const areEqualTrees = (tree, otherTree) => {\n    if (tree.type !== otherTree.type) {\n        return false;\n    }\n    if (tree.negate !== otherTree.negate) {\n        return false;\n    }\n    if (tree.type === \"condition\") {\n        if (!areEqualValues(tree.path, otherTree.path)) {\n            return false;\n        }\n        if (!areEqualValues(tree.operator, otherTree.operator)) {\n            return false;\n        }\n        if (isTree(tree.value)) {\n            if (isTree(otherTree.value)) {\n                return areEqualTrees(tree.value, otherTree.value);\n            }\n            return false;\n        } else if (isTree(otherTree.value)) {\n            return false;\n        }\n        if (!areEqualValues(tree.value, otherTree.value)) {\n            return false;\n        }\n        return true;\n    }\n    if (!areEqualValues(tree.value, otherTree.value)) {\n        return false;\n    }\n    if (tree.type === \"complex_condition\") {\n        return true;\n    }\n    return areEqualArraysOfTrees(tree.children, otherTree.children);\n};\n\n/**\n * @param {import(\"@web/core/py_js/py_parser\").AST} ast\n * @returns {Value}\n */\nexport function toValue(ast, isWithinArray = false) {\n    if ([4, 10].includes(ast.type) && !isWithinArray) {\n        /** 4: list, 10: tuple */\n        return ast.value.map((v) => toValue(v, true));\n    } else if ([0, 1, 2].includes(ast.type)) {\n        /** 0: number, 1: string, 2: boolean */\n        return ast.value;\n    } else if (ast.type === 6 && ast.op === \"-\" && ast.right.type === 0) {\n        /** 6: unary operator */\n        return -ast.right.value;\n    } else if (ast.type === 5 && [\"false\", \"true\"].includes(ast.value)) {\n        /** 5: name */\n        return JSON.parse(ast.value);\n    } else {\n        return new Expression(ast);\n    }\n}\n\nexport function astFromValue(value) {\n    if (value instanceof Expression) {\n        return value.toAST();\n    }\n    if (Array.isArray(value)) {\n        return { type: 4, value: value.map(astFromValue) };\n    }\n    return toPyValue(value);\n}\n\nexport function formatValue(value) {\n    return formatAST(astFromValue(value));\n}\n\nexport function normalizeValue(value) {\n    return toValue(astFromValue(value)); // no array in array (see isWithinArray)\n}\n\nexport function isTree(value) {\n    return (\n        typeof value === \"object\" &&\n        !(value instanceof Domain) &&\n        !(value instanceof Expression) &&\n        !Array.isArray(value) &&\n        value !== null\n    );\n}\n\n/**\n * @param {Connector} parent\n * @param {Tree} child\n */\nexport function addChild(parent, child) {\n    if (child.type === \"connector\" && !child.negate && child.value === parent.value) {\n        parent.children.push(...child.children);\n    } else {\n        parent.children.push(child);\n    }\n}\n\nexport function applyTransformations(transformations, transformed, ...fixedParams) {\n    for (let i = transformations.length - 1; i >= 0; i--) {\n        const fn = transformations[i];\n        transformed = fn(transformed, ...fixedParams);\n    }\n    return transformed;\n}\n\nfunction normalizeConnector(connector) {\n    const newTree = { ...connector, children: [] };\n    for (const child of connector.children) {\n        addChild(newTree, child);\n    }\n    if (newTree.children.length === 1) {\n        const child = newTree.children[0];\n        if (newTree.negate) {\n            const newChild = { ...child, negate: !child.negate };\n            if (newChild.type === \"condition\") {\n                return newChild;\n            }\n            return newChild;\n        }\n        return child;\n    }\n    return newTree;\n}\n\nfunction makeOptions(path, options) {\n    return {\n        ...options,\n        getFieldDef: (p) => {\n            if (typeof path === \"string\" && typeof p === \"string\") {\n                return options.getFieldDef?.(`${path}.${p}`) || null;\n            }\n            return null;\n        },\n    };\n}\n\n/**\n * @param {Function} transformation\n * @param {Tree} tree\n * @param {Options} [options={}]\n * @param {\"condition\"|\"connector\"|\"complex_condition\"} [treeType=\"condition\"]\n * @returns {Tree}\n */\nexport function operate(\n    transformation,\n    tree,\n    options = {},\n    treeType = \"condition\",\n    traverseSubTrees = true\n) {\n    if (tree.type === \"connector\") {\n        const newTree = {\n            ...tree,\n            children: tree.children.map((c) =>\n                operate(transformation, c, options, treeType, traverseSubTrees)\n            ),\n        };\n        if (treeType === \"connector\") {\n            return normalizeConnector(transformation(newTree, options) || newTree);\n        }\n        return normalizeConnector(newTree);\n    }\n    const clone = cloneTree(tree);\n    if (traverseSubTrees && tree.type === \"condition\" && isTree(tree.value)) {\n        clone.value = operate(\n            transformation,\n            tree.value,\n            makeOptions(tree.path, options),\n            treeType,\n            traverseSubTrees\n        );\n    }\n    if (treeType === tree.type) {\n        return transformation(clone, options) || clone;\n    }\n    return clone;\n}\n\nexport function rewriteNConsecutiveChildren(transformation, N = 2) {\n    return (c, options) => {\n        const children = [];\n        const currentChildren = c.children;\n        for (let i = 0; i < currentChildren.length; i++) {\n            const NconsecutiveChildren = currentChildren.slice(i, i + N);\n            let replacement = null;\n            if (NconsecutiveChildren.length === N) {\n                replacement = transformation(connector(c.value, NconsecutiveChildren), options);\n            }\n            if (replacement) {\n                children.push(replacement);\n                i += N - 1;\n            } else {\n                children.push(NconsecutiveChildren[0]);\n            }\n        }\n        return { ...c, children };\n    };\n}\n", "import { formatAST, parseExpr } from \"@web/core/py_js/py\";\nimport { isBool, isNot } from \"./ast_utils\";\nimport {\n    astFromValue,\n    condition,\n    Expression,\n    FALSE_TREE,\n    isTree,\n    TRUE_TREE,\n} from \"./condition_tree\";\n\nfunction bool(ast) {\n    if (isBool(ast) || isNot(ast) || ast.type === 2) {\n        return ast;\n    }\n    return { type: 8, fn: { type: 5, value: \"bool\" }, args: [ast], kwargs: {} };\n}\n\nfunction getASTs(tree, isSubTree = false) {\n    const ASTs = [];\n    if (tree.type === \"condition\") {\n        if (tree.negate) {\n            ASTs.push(toAST(\"!\"));\n        }\n        ASTs.push({\n            type: 10,\n            value: [tree.path, tree.operator, tree.value].map(toAST),\n        });\n        return ASTs;\n    }\n\n    if (tree.type === \"complex_condition\") {\n        const ast = parseExpr(tree.value);\n        return getASTs(condition(new Expression(bool(ast)), \"=\", 1));\n    }\n\n    const length = tree.children.length;\n    if (length === 0) {\n        if (tree.value === \"|\") {\n            return tree.negate ? getASTs(TRUE_TREE) : getASTs(FALSE_TREE);\n        } else {\n            return tree.negate ? getASTs(FALSE_TREE) : isSubTree ? getASTs(TRUE_TREE) : [];\n        }\n    }\n\n    if (tree.negate) {\n        ASTs.push(toAST(\"!\"));\n    }\n    for (let i = 0; i < length - 1; i++) {\n        ASTs.push(toAST(tree.value));\n    }\n    for (const child of tree.children) {\n        ASTs.push(...getASTs(child, true));\n    }\n    return ASTs;\n}\n\nfunction toAST(value) {\n    if (isTree(value)) {\n        return { type: 4, value: getASTs(value) };\n    }\n    return astFromValue(value);\n}\n\nexport function constructDomainFromTree(tree) {\n    return formatAST(toAST(tree));\n}\n", "import { formatAST } from \"@web/core/py_js/py\";\nimport { isValidPath, not } from \"./ast_utils\";\nimport { Expression, astFromValue, isTree } from \"./condition_tree\";\nimport { COMPARATORS, TERM_OPERATORS_NEGATION } from \"./operators\";\n\nfunction getNormalizedCondition(condition) {\n    let { operator, negate } = condition;\n    if (negate && typeof operator === \"string\" && TERM_OPERATORS_NEGATION[operator]) {\n        operator = TERM_OPERATORS_NEGATION[operator];\n        negate = false;\n    }\n    return { ...condition, operator, negate };\n}\n\nfunction isX2Many(ast, options) {\n    if (isValidPath(ast, options)) {\n        const fieldDef = options.getFieldDef(ast.value); // safe: isValidPath has not returned null;\n        return [\"many2many\", \"one2many\"].includes(fieldDef.type);\n    }\n    return false;\n}\n\nfunction _constructExpressionFromTree(tree, options, isRoot = false) {\n    if (tree.type === \"connector\" && tree.value === \"|\" && tree.children.length === 2) {\n        // check if we have an \"if else\"\n        const isSimpleAnd = (tree) =>\n            tree.type === \"connector\" && tree.value === \"&\" && tree.children.length === 2;\n        if (tree.children.every((c) => isSimpleAnd(c))) {\n            const [c1, c2] = tree.children;\n            for (let i = 0; i < 2; i++) {\n                const c1Child = c1.children[i];\n                const str1 = _constructExpressionFromTree({ ...c1Child }, options);\n                for (let j = 0; j < 2; j++) {\n                    const c2Child = c2.children[j];\n                    const str2 = _constructExpressionFromTree(c2Child, options);\n                    if (str1 === `not ${str2}` || `not ${str1}` === str2) {\n                        /** @todo smth smarter. this is very fragile */\n                        const others = [c1.children[1 - i], c2.children[1 - j]];\n                        const str = _constructExpressionFromTree(c1Child, options);\n                        const strs = others.map((c) => _constructExpressionFromTree(c, options));\n                        return `${strs[0]} if ${str} else ${strs[1]}`;\n                    }\n                }\n            }\n        }\n    }\n\n    if (tree.type === \"connector\") {\n        const connector = tree.value === \"&\" ? \"and\" : \"or\";\n        const subExpressions = tree.children.map((c) => _constructExpressionFromTree(c, options));\n        if (!subExpressions.length) {\n            return connector === \"and\" ? \"1\" : \"0\";\n        }\n        let expression = subExpressions.join(` ${connector} `);\n        if (!isRoot || tree.negate) {\n            expression = `( ${expression} )`;\n        }\n        if (tree.negate) {\n            expression = `not ${expression}`;\n        }\n        return expression;\n    }\n\n    if (tree.type === \"complex_condition\") {\n        return tree.value;\n    }\n\n    tree = getNormalizedCondition(tree);\n    const { path, operator, value } = tree;\n\n    if (path instanceof Expression && operator === \"=\" && value === 1) {\n        return path.toString();\n    }\n\n    const op = operator === \"=\" ? \"==\" : operator; // do something about is ?\n    if (typeof op !== \"string\" || !COMPARATORS.includes(op)) {\n        throw new Error(\"Invalid operator\");\n    }\n\n    // we can assume that negate = false here: comparators have negation defined\n    // and the tree has been normalized\n\n    if ([0, 1].includes(path)) {\n        if (operator !== \"=\" || value !== 1) {\n            // check if this is too restricive for us\n            return new Error(\"Invalid condition\");\n        }\n        return formatAST({ type: 2, value: Boolean(path) });\n    }\n\n    const pathAST = astFromValue(path);\n    if (typeof path == \"string\" && isValidPath({ type: 5, value: path }, options)) {\n        pathAST.type = 5;\n    }\n\n    if (value === false && [\"=\", \"!=\"].includes(operator)) {\n        // true makes sense for non boolean fields?\n        return formatAST(operator === \"=\" ? not(pathAST) : pathAST);\n    }\n\n    if (isTree(value)) {\n        throw new Error(\"Invalid value\");\n    }\n\n    let valueAST = astFromValue(value);\n    if (\n        [\"in\", \"not in\"].includes(operator) &&\n        !(value instanceof Expression) &&\n        ![4, 10].includes(valueAST.type)\n    ) {\n        valueAST = { type: 4, value: [valueAST] };\n    }\n\n    if (pathAST.type === 5 && isX2Many(pathAST, options) && [\"in\", \"not in\"].includes(operator)) {\n        const ast = {\n            type: 8,\n            fn: {\n                type: 15,\n                obj: {\n                    args: [pathAST],\n                    type: 8,\n                    fn: {\n                        type: 5,\n                        value: \"set\",\n                    },\n                },\n                key: \"intersection\",\n            },\n            args: [valueAST],\n        };\n        return formatAST(operator === \"not in\" ? not(ast) : ast);\n    }\n\n    // add case true for boolean fields\n\n    return formatAST({\n        type: 7,\n        op,\n        left: pathAST,\n        right: valueAST,\n    });\n}\n\nexport function constructExpressionFromTree(tree, options = {}) {\n    return _constructExpressionFromTree(tree, options, true);\n}\n", "import { Domain } from \"@web/core/domain\";\nimport { formatAST } from \"@web/core/py_js/py\";\nimport { addChild, connector, toValue } from \"./condition_tree\";\n\n/** @typedef { import(\"@web/core/py_js/py_parser\").AST } AST */\n/** @typedef {import(\"@web/core/domain\").DomainRepr} DomainRepr */\n/** @typedef {import(\"./condition_tree\").Tree} Tree */\n\n/**\n * @param {AST[]} ASTs\n * @param {boolean} [distributeNot=false]\n * @param {boolean} [negate=false]\n * @returns {{ tree: Tree, remaimingASTs: AST[] }}\n */\nfunction _constructTree(ASTs, distributeNot = false, negate = false) {\n    const [firstAST, ...tailASTs] = ASTs;\n\n    if (firstAST.type === 1 && firstAST.value === \"!\") {\n        return _constructTree(tailASTs, distributeNot, !negate);\n    }\n\n    const tree = { type: firstAST.type === 1 ? \"connector\" : \"condition\" };\n    if (tree.type === \"connector\") {\n        tree.value = firstAST.value;\n        if (distributeNot && negate) {\n            tree.value = tree.value === \"&\" ? \"|\" : \"&\";\n            tree.negate = false;\n        } else {\n            tree.negate = negate;\n        }\n        tree.children = [];\n    } else {\n        const [pathAST, operatorAST, valueAST] = firstAST.value;\n        tree.path = toValue(pathAST);\n        tree.negate = negate;\n        tree.operator = toValue(operatorAST);\n        tree.value = toValue(valueAST);\n        if ([\"any\", \"not any\"].includes(tree.operator)) {\n            try {\n                tree.value = constructTreeFromDomain(formatAST(valueAST), distributeNot);\n            } catch {\n                tree.value = Array.isArray(tree.value) ? tree.value : [tree.value];\n            }\n        }\n    }\n    let remaimingASTs = tailASTs;\n    if (tree.type === \"connector\") {\n        for (let i = 0; i < 2; i++) {\n            const { tree: child, remaimingASTs: otherASTs } = _constructTree(\n                remaimingASTs,\n                distributeNot,\n                distributeNot && negate\n            );\n            remaimingASTs = otherASTs;\n            addChild(tree, child);\n        }\n    }\n    return { tree, remaimingASTs };\n}\n\n/**\n * @param {DomainRepr} domain\n * @param {boolean} [distributeNot=false]\n * @returns {Tree}\n */\nexport function constructTreeFromDomain(domain, distributeNot = false) {\n    domain = new Domain(domain);\n    const domainAST = domain.ast;\n    // @ts-ignore\n    const initialASTs = domainAST.value;\n    if (!initialASTs.length) {\n        return connector(\"&\");\n    }\n    const { tree } = _constructTree(initialASTs, distributeNot);\n    return tree;\n}\n", "import { formatAST, parseExpr } from \"@web/core/py_js/py\";\nimport { isNot, isValidPath, not } from \"./ast_utils\";\nimport { addChild, complexCondition, condition, connector, toValue } from \"./condition_tree\";\nimport { COMPARATORS } from \"./operators\";\n\nconst EXCHANGE = {\n    \"<\": \">\",\n    \"<=\": \">=\",\n    \">\": \"<\",\n    \">=\": \"<=\",\n    \"=\": \"=\",\n    \"!=\": \"!=\",\n};\n\nfunction or(left, right) {\n    return { type: 14, op: \"or\", left, right };\n}\n\nfunction and(left, right) {\n    return { type: 14, op: \"and\", left, right };\n}\n\nfunction isSet(ast) {\n    return ast.type === 8 && ast.fn.type === 5 && ast.fn.value === \"set\" && ast.args.length <= 1;\n}\n\nfunction isValidPath2(ast, options) {\n    if (!ast) {\n        return null;\n    }\n    if ([4, 10].includes(ast.type) && ast.value.length === 1) {\n        return isValidPath(ast.value[0], options);\n    }\n    return isValidPath(ast, options);\n}\n\nfunction _getConditionFromComparator(ast, options) {\n    if ([\"is\", \"is not\"].includes(ast.op)) {\n        // we could do something smarter here\n        // e.g. if left is a boolean field and right is a boolean\n        // we can create a condition based on \"=\"\n        return null;\n    }\n\n    let operator = ast.op;\n    if (operator === \"==\") {\n        operator = \"=\";\n    }\n\n    let left = ast.left;\n    let right = ast.right;\n    if (isValidPath(left, options) == isValidPath(right, options)) {\n        return null;\n    }\n\n    if (!isValidPath(left, options)) {\n        if (operator in EXCHANGE) {\n            const temp = left;\n            left = right;\n            right = temp;\n            operator = EXCHANGE[operator];\n        } else {\n            return null;\n        }\n    }\n\n    return condition(left.value, operator, toValue(right));\n}\n\nfunction _getConditionFromIntersection(ast, options, negate = false) {\n    let left = ast.fn.obj.args[0];\n    let right = ast.args[0];\n\n    if (!left) {\n        return condition(negate ? 1 : 0, \"=\", 1);\n    }\n\n    // left/right exchange\n    if (isValidPath2(left, options) == isValidPath2(right, options)) {\n        return null;\n    }\n    if (!isValidPath2(left, options)) {\n        const temp = left;\n        left = right;\n        right = temp;\n    }\n\n    if ([4, 10].includes(left.type) && left.value.length === 1) {\n        left = left.value[0];\n    }\n\n    if (!right) {\n        return condition(left.value, negate ? \"=\" : \"!=\", false);\n    }\n\n    // try to extract the ast of an iterable\n    // we only make simple conversions here\n    if (isSet(right)) {\n        if (!right.args[0]) {\n            right = { type: 4, value: [] };\n        }\n        if ([4, 10].includes(right.args[0].type)) {\n            right = right.args[0];\n        }\n    }\n\n    if (![4, 10].includes(right.type)) {\n        return null;\n    }\n\n    return condition(left.value, negate ? \"not in\" : \"in\", toValue(right));\n}\n\nfunction _leafFromAST(ast, options, negate = false) {\n    if (isNot(ast)) {\n        return _treeFromAST(ast.right, options, !negate);\n    }\n\n    if (ast.type === 5 /** name */ && isValidPath(ast, options)) {\n        return condition(ast.value, negate ? \"=\" : \"!=\", false);\n    }\n\n    const astValue = toValue(ast);\n    if ([\"boolean\", \"number\", \"string\"].includes(typeof astValue)) {\n        return condition(astValue ? 1 : 0, \"=\", 1);\n    }\n\n    if (\n        ast.type === 8 &&\n        ast.fn.type === 15 /** object lookup */ &&\n        isSet(ast.fn.obj) &&\n        ast.fn.key === \"intersection\"\n    ) {\n        const tree = _getConditionFromIntersection(ast, options, negate);\n        if (tree) {\n            return tree;\n        }\n    }\n\n    if (ast.type === 7 && COMPARATORS.includes(ast.op)) {\n        if (negate) {\n            return _leafFromAST(not(ast), options);\n        }\n        const tree = _getConditionFromComparator(ast, options);\n        if (tree) {\n            return tree;\n        }\n    }\n\n    // no conclusive/simple way to transform ast in a condition\n    return complexCondition(formatAST(negate ? not(ast) : ast));\n}\n\nfunction _treeFromAST(ast, options, negate = false) {\n    if (isNot(ast)) {\n        return _treeFromAST(ast.right, options, !negate);\n    }\n\n    if (ast.type === 14) {\n        const tree = connector(\n            ast.op === \"and\" ? \"&\" : \"|\" // and/or are the only ops that are given type 14 (for now)\n        );\n        if (options.distributeNot && negate) {\n            tree.value = tree.value === \"&\" ? \"|\" : \"&\";\n        } else {\n            tree.negate = negate;\n        }\n        const subASTs = [ast.left, ast.right];\n        for (const subAST of subASTs) {\n            const child = _treeFromAST(subAST, options, options.distributeNot && negate);\n            addChild(tree, child);\n        }\n        return tree;\n    }\n\n    if (ast.type === 13) {\n        const newAST = or(and(ast.condition, ast.ifTrue), and(not(ast.condition), ast.ifFalse));\n        return _treeFromAST(newAST, options, negate);\n    }\n\n    return _leafFromAST(ast, options, negate);\n}\n\nexport function constructTreeFromExpression(expression, options = {}) {\n    const ast = parseExpr(expression);\n    return _treeFromAST(ast, options);\n}\n", "import { Expression, isTree } from \"./condition_tree\";\nimport { constructTreeFromDomain } from \"./construct_tree_from_domain\";\n\nfunction treeContainsExpressions(tree) {\n    if (tree.type === \"condition\") {\n        const { path, operator, value } = tree;\n        if (isTree(value) && treeContainsExpressions(value)) {\n            return true;\n        }\n        return [path, operator, value].some(\n            (v) =>\n                v instanceof Expression ||\n                (Array.isArray(v) && v.some((w) => w instanceof Expression))\n        );\n    }\n    for (const child of tree.children) {\n        if (treeContainsExpressions(child)) {\n            return true;\n        }\n    }\n    return false;\n}\n\nexport function domainContainsExpressions(domain) {\n    let tree;\n    try {\n        tree = constructTreeFromDomain(domain);\n    } catch {\n        return null;\n    }\n    // detect expressions in the domain tree, which we know is well-formed\n    return treeContainsExpressions(tree);\n}\n", "import { constructDomainFromTree } from \"./construct_domain_from_tree\";\nimport { eliminateVirtualOperators } from \"./virtual_operators\";\n\nexport function domainFromTree(tree) {\n    const simplifiedTree = eliminateVirtualOperators(tree);\n    return constructDomainFromTree(simplifiedTree);\n}\n", "import { constructExpressionFromTree } from \"./construct_expression_from_tree\";\nimport { eliminateVirtualOperators } from \"./virtual_operators\";\n\nexport function expressionFromTree(tree, options = {}) {\n    const simplifiedTree = eliminateVirtualOperators(tree, options);\n    return constructExpressionFromTree(simplifiedTree, options);\n}\n", "export const TERM_OPERATORS_NEGATION = {\n    \"<\": \">=\",\n    \">\": \"<=\",\n    \"<=\": \">\",\n    \">=\": \"<\",\n    \"=\": \"!=\",\n    \"!=\": \"=\",\n    in: \"not in\",\n    like: \"not like\",\n    ilike: \"not ilike\",\n    \"not in\": \"in\",\n    \"not like\": \"like\",\n    \"not ilike\": \"ilike\",\n};\n\nexport const TERM_OPERATORS_NEGATION_EXTENDED = {\n    ...TERM_OPERATORS_NEGATION,\n    is: \"is not\",\n    \"is not\": \"is\",\n    \"==\": \"!=\",\n    \"!=\": \"==\", // override here\n};\n\nexport const COMPARATORS = [\"<\", \"<=\", \">\", \">=\", \"in\", \"not in\", \"==\", \"is\", \"!=\", \"is not\"];\n", "import { Component, onWillStart, onWillUpdateProps } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { cloneTree, connector, isTree, TRUE_TREE } from \"@web/core/tree_editor/condition_tree\";\nimport {\n    getDefaultValue,\n    getValueEditorInfo,\n} from \"@web/core/tree_editor/tree_editor_value_editors\";\nimport { getResModel } from \"@web/core/tree_editor/utils\";\nimport { areEquivalentTrees } from \"@web/core/tree_editor/virtual_operators\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { shallowEqual } from \"@web/core/utils/objects\";\n\nexport class TreeEditor extends Component {\n    static template = \"web.TreeEditor\";\n    static components = {\n        Dropdown,\n        DropdownItem,\n        TreeEditor,\n    };\n    static props = {\n        tree: Object,\n        resModel: String,\n        update: Function,\n        getDefaultCondition: Function,\n        getPathEditorInfo: Function,\n        getOperatorEditorInfo: Function,\n        getDefaultOperator: Function,\n        readonly: { type: Boolean, optional: true },\n        slots: { type: Object, optional: true },\n        isDebugMode: { type: Boolean, optional: true },\n        defaultConnector: { type: [{ value: \"&\" }, { value: \"|\" }], optional: true },\n        isSubTree: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        defaultConnector: \"&\",\n        readonly: false,\n        isSubTree: false,\n    };\n\n    setup() {\n        this.isTree = isTree;\n        this.fieldService = useService(\"field\");\n        this.treeProcessor = useService(\"tree_processor\");\n        onWillStart(() => this.onPropsUpdated(this.props));\n        onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps));\n    }\n\n    async onPropsUpdated(props) {\n        if (this.tree) {\n            this.previousTree = this.tree;\n        }\n        this.tree = cloneTree(props.tree);\n        if (shallowEqual(this.tree, TRUE_TREE)) {\n            this.tree = connector(props.defaultConnector);\n        } else if (this.tree.type !== \"connector\") {\n            this.tree = connector(props.defaultConnector, [this.tree]);\n        }\n\n        if (this.previousTree && areEquivalentTrees(this.tree, this.previousTree)) {\n            this.tree = this.previousTree;\n            this.previousTree = null;\n        }\n\n        await this.prepareInfo(props);\n    }\n\n    async prepareInfo(props) {\n        const [fieldDefs, getFieldDef] = await Promise.all([\n            this.fieldService.loadFields(props.resModel),\n            this.treeProcessor.makeGetFieldDef(props.resModel, this.tree),\n        ]);\n        this.getFieldDef = getFieldDef;\n        this.defaultCondition = props.getDefaultCondition(fieldDefs);\n\n        if (props.readonly) {\n            this.getConditionDescription = await this.treeProcessor.makeGetConditionDescription(\n                props.resModel,\n                this.tree\n            );\n        }\n    }\n\n    get className() {\n        return `${this.props.readonly ? \"o_read_mode\" : \"o_edit_mode\"}`;\n    }\n\n    get isDebugMode() {\n        return this.props.isDebugMode !== undefined ? this.props.isDebugMode : !!this.env.debug;\n    }\n\n    notifyChanges() {\n        this.props.update(this.tree);\n    }\n\n    _updateConnector(node) {\n        node.value = node.value === \"&\" ? \"|\" : \"&\";\n        node.negate = false;\n    }\n\n    updateConnector(node) {\n        this.updateNode(node, () => this._updateConnector(node));\n    }\n\n    _updateComplexCondition(node, value) {\n        node.value = value;\n    }\n\n    updateComplexCondition(node, value) {\n        this.updateNode(node, () => this._updateComplexCondition(node, value));\n    }\n\n    makeCondition(parent, condition) {\n        condition ||= parent.children.findLast((c) => c.type === \"condition\");\n        return cloneTree(condition || this.defaultCondition);\n    }\n\n    _addNewCondition(parent, node) {\n        if (node) {\n            const index = parent.children.indexOf(node);\n            parent.children.splice(index + 1, 0, this.makeCondition(parent, node));\n        } else {\n            parent.children.push(this.makeCondition(parent));\n        }\n    }\n\n    addNewCondition(parent, node) {\n        this.updateNode(parent, () => this._addNewCondition(parent, node));\n    }\n\n    _addNewConnector(parent, node) {\n        const index = parent.children.indexOf(node);\n        const nextConnector = parent.value === \"&\" ? \"|\" : \"&\";\n        parent.children.splice(\n            index + 1,\n            0,\n            connector(nextConnector, [this.makeCondition(parent, node)])\n        );\n    }\n\n    addNewConnector(parent, node) {\n        this.updateNode(parent, () => this._addNewConnector(parent, node));\n    }\n\n    _delete(ancestors, node) {\n        if (ancestors.length === 0) {\n            return;\n        }\n        const parent = ancestors.at(-1);\n        const index = parent.children.indexOf(node);\n        parent.children.splice(index, 1);\n        ancestors = ancestors.slice(0, ancestors.length - 1);\n        if (parent.children.length === 0) {\n            this._delete(ancestors, parent);\n        }\n    }\n\n    delete(ancestors, node) {\n        const upperNode = ancestors[0] || node;\n        this.updateNode(upperNode, () => this._delete(ancestors, node));\n    }\n\n    getResModel(node) {\n        const fieldDef = this.getFieldDef(node.path);\n        const resModel = getResModel(fieldDef);\n        return resModel;\n    }\n\n    getPathEditorInfo() {\n        return this.props.getPathEditorInfo(this.props.resModel, this.defaultCondition);\n    }\n\n    getOperatorEditorInfo(node) {\n        const fieldDef = this.getFieldDef(node.path);\n        return this.props.getOperatorEditorInfo(fieldDef);\n    }\n\n    getValueEditorInfo(node) {\n        const fieldDef = this.getFieldDef(node.path);\n        return getValueEditorInfo(fieldDef, node.operator);\n    }\n\n    async _updatePath(node, path) {\n        const { fieldDef } = await this.fieldService.loadFieldInfo(this.props.resModel, path);\n        node.path = path;\n        node.negate = false;\n        node.operator = this.props.getDefaultOperator(fieldDef);\n        node.value = getDefaultValue(fieldDef, node.operator);\n    }\n\n    async updatePath(node, path) {\n        this.updateNode(node, () => this._updatePath(node, path));\n    }\n\n    _updateLeafOperator(node, operator, negate) {\n        const fieldDef = this.getFieldDef(node.path);\n        node.negate = negate;\n        node.operator = operator;\n        node.value = getDefaultValue(fieldDef, operator, node.value);\n    }\n\n    updateLeafOperator(node, operator, negate) {\n        this.updateNode(node, () => this._updateLeafOperator(node, operator, negate));\n    }\n\n    _updateLeafValue(node, value) {\n        node.value = value;\n    }\n\n    updateLeafValue(node, value) {\n        this.updateNode(node, () => this._updateLeafValue(node, value));\n    }\n\n    async updateNode(node, operation) {\n        const previousNode = cloneTree(node);\n        await operation();\n        if (areEquivalentTrees(node, previousNode)) {\n            // no interesting changes for parent\n            // this means that the parent might not render the domain selector\n            // but we need to udpate editors\n            await this.prepareInfo(this.props);\n            this.render();\n        }\n        this.notifyChanges();\n    }\n\n    highlightNode(target) {\n        const nodeEl = target.closest(\".o_tree_editor_node\");\n        nodeEl.classList.toggle(\"o_hovered_button\");\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { formatAST, toPyValue } from \"@web/core/py_js/py_utils\";\nimport { MultiRecordSelector } from \"@web/core/record_selectors/multi_record_selector\";\nimport { RecordSelector } from \"@web/core/record_selectors/record_selector\";\nimport { Expression } from \"@web/core/tree_editor/condition_tree\";\nimport { isId } from \"@web/core/tree_editor/utils\";\nimport { imageUrl } from \"@web/core/utils/urls\";\n\nexport const getFormat = (val, displayNames) => {\n    let text;\n    let colorIndex;\n    if (isId(val)) {\n        text =\n            typeof displayNames[val] === \"string\"\n                ? displayNames[val]\n                : _t(\"Inaccessible/missing record ID: %s\", val);\n        colorIndex = typeof displayNames[val] === \"string\" ? 0 : 2; // 0 = grey, 2 = orange\n    } else {\n        text =\n            val instanceof Expression\n                ? String(val)\n                : _t(\"Invalid record ID: %s\", formatAST(toPyValue(val)));\n        colorIndex = val instanceof Expression ? 2 : 1; // 1 = red\n    }\n    return { text, colorIndex };\n};\n\nexport class DomainSelectorAutocomplete extends MultiRecordSelector {\n    static props = {\n        ...MultiRecordSelector.props,\n        resIds: true, //resIds could be an array of ids or an array of expressions\n    };\n\n    getIds(props = this.props) {\n        return props.resIds.filter((val) => isId(val));\n    }\n\n    getTags(props, displayNames) {\n        return props.resIds.map((val, index) => {\n            const { text, colorIndex } = getFormat(val, displayNames);\n            return {\n                text,\n                colorIndex,\n                onDelete: () => {\n                    this.props.update([\n                        ...this.props.resIds.slice(0, index),\n                        ...this.props.resIds.slice(index + 1),\n                    ]);\n                },\n                img:\n                    this.isAvatarModel &&\n                    isId(val) &&\n                    imageUrl(this.props.resModel, val, \"avatar_128\"),\n            };\n        });\n    }\n}\n\nexport class DomainSelectorSingleAutocomplete extends RecordSelector {\n    static props = {\n        ...RecordSelector.props,\n        resId: true,\n    };\n\n    getDisplayName(props = this.props, displayNames) {\n        const { resId } = props;\n        if (resId === false) {\n            return \"\";\n        }\n        const { text } = getFormat(resId, displayNames);\n        return text;\n    }\n\n    getIds(props = this.props) {\n        if (isId(props.resId)) {\n            return [props.resId];\n        }\n        return [];\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { TagsList } from \"@web/core/tags_list/tags_list\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport class Input extends Component {\n    static props = [\"value\", \"update\", \"placeholder?\", \"startEmpty?\"];\n    static template = \"web.TreeEditor.Input\";\n}\n\nexport class Select extends Component {\n    static props = [\"value\", \"update\", \"options\", \"placeholder?\", \"addBlankOption?\"];\n    static template = \"web.TreeEditor.Select\";\n\n    deserialize(value) {\n        return JSON.parse(value);\n    }\n\n    serialize(value) {\n        return JSON.stringify(value);\n    }\n}\n\nexport class Range extends Component {\n    static props = [\"value\", \"update\", \"editorInfo\"];\n    static template = \"web.TreeEditor.Range\";\n\n    update(index, newValue) {\n        const result = [...this.props.value];\n        result[index] = newValue;\n        return this.props.update(result);\n    }\n}\n\nexport class InRange extends Component {\n    static props = [\"value\", \"update\", \"valueTypeEditorInfo\", \"betweenEditorInfo\"];\n    static template = \"web.TreeEditor.InRange\";\n    static options = [\n        [\"today\", _t(\"Today\")],\n        [\"last 7 days\", _t(\"Last 7 days\")],\n        [\"last 30 days\", _t(\"Last 30 days\")],\n        [\"month to date\", _t(\"Month to date\")],\n        [\"last month\", _t(\"Last month\")],\n        [\"year to date\", _t(\"Year to date\")],\n        [\"last 12 months\", _t(\"Last 12 months\")],\n        [\"custom range\", _t(\"Custom range\")],\n    ];\n    updateValueType(newValueType) {\n        const [fieldType, currentValueType] = this.props.value;\n        if (currentValueType !== newValueType) {\n            const values =\n                newValueType === \"custom range\"\n                    ? this.props.betweenEditorInfo.defaultValue()\n                    : [false, false];\n            return this.props.update([fieldType, newValueType, ...values]);\n        }\n    }\n    updateValues(values) {\n        const [fieldType, currentValueType] = this.props.value;\n        return this.props.update([fieldType, currentValueType, ...values]);\n    }\n}\n\nexport class List extends Component {\n    static components = { TagsList };\n    static props = [\"value\", \"update\", \"editorInfo\"];\n    static template = \"web.TreeEditor.List\";\n\n    get tags() {\n        const { isSupported, stringify } = this.props.editorInfo;\n        return this.props.value.map((val, index) => ({\n            text: stringify(val),\n            colorIndex: isSupported(val) ? 0 : 2,\n            onDelete: () => {\n                this.props.update([\n                    ...this.props.value.slice(0, index),\n                    ...this.props.value.slice(index + 1),\n                ]);\n            },\n        }));\n    }\n\n    update(newValue) {\n        return this.props.update([...this.props.value, newValue]);\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { parseExpr } from \"@web/core/py_js/py\";\nimport { formatValue, toValue } from \"@web/core/tree_editor/condition_tree\";\nimport { Select } from \"@web/core/tree_editor/tree_editor_components\";\n\nconst OPERATOR_DESCRIPTIONS = {\n    // valid operators (see TERM_OPERATORS in expression.py)\n    \"=\": (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"many2one\":\n            case \"many2many\":\n            case \"one2many\":\n                return _t(\"=\");\n            default:\n                return _t(\"is equal to\");\n        }\n    },\n    \"!=\": (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"many2one\":\n            case \"many2many\":\n            case \"one2many\":\n                return _t(\"!=\");\n            default:\n                return _t(\"is not equal to\");\n        }\n    },\n    \"<=\": _t(\"lower or equal to\"),\n    \"<\": (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"date\":\n            case \"datetime\":\n                return _t(\"before\");\n            default:\n                return _t(\"lower than\");\n        }\n    },\n    \">\": (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"date\":\n            case \"datetime\":\n                return _t(\"after\");\n            default:\n                return _t(\"greater than\");\n        }\n    },\n    \">=\": _t(\"greater or equal to\"),\n    \"=?\": \"=?\",\n    \"=like\": _t(\"=like\"),\n    \"=ilike\": _t(\"=ilike\"),\n    like: _t(\"like\"),\n    \"not like\": _t(\"not like\"),\n    ilike: _t(\"contains\"),\n    \"not ilike\": _t(\"does not contain\"),\n    in: (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"many2one\":\n            case \"many2many\":\n            case \"one2many\":\n                return _t(\"is equal to\");\n            default:\n                return _t(\"is in\");\n        }\n    },\n    \"not in\": (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"many2one\":\n            case \"many2many\":\n            case \"one2many\":\n                return _t(\"is not equal to\");\n            default:\n                return _t(\"is not in\");\n        }\n    },\n    child_of: _t(\"child of\"),\n    parent_of: _t(\"parent of\"),\n    any: (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"many2one\":\n                return _t(\"matches\");\n            default:\n                return _t(\"match\");\n        }\n    },\n    \"not any\": (fieldDefType) => {\n        switch (fieldDefType) {\n            case \"many2one\":\n                return _t(\"matches none of\");\n            default:\n                return _t(\"match none of\");\n        }\n    },\n\n    // virtual operators\n    set: _t(\"is set\"),\n    \"not set\": _t(\"is not set\"),\n\n    \"starts with\": _t(\"starts with\"),\n\n    between: _t(\"between\"),\n    \"in range\": _t(\"is in\"),\n};\n\nfunction toKey(operator, negate = false) {\n    if (!negate && typeof operator === \"string\" && operator in OPERATOR_DESCRIPTIONS) {\n        // this case is the main one. We keep it simple\n        return operator;\n    }\n    return JSON.stringify([formatValue(operator), negate]);\n}\n\nfunction toOperator(key) {\n    if (!key.includes(\"[\")) {\n        return [key, false];\n    }\n    const [expr, negate] = JSON.parse(key);\n    return [toValue(parseExpr(expr)), negate];\n}\n\nfunction getOperatorDescription(operator, fieldDefType) {\n    const description = OPERATOR_DESCRIPTIONS[operator];\n    if (\n        typeof description === \"function\" &&\n        description.constructor?.name !== \"LazyTranslatedString\"\n    ) {\n        return description(fieldDefType);\n    }\n    return description;\n}\n\nexport function getOperatorLabel(\n    operator,\n    fieldDefType,\n    negate = false,\n    getDescr = (operator, fieldDefType) => null\n) {\n    let label;\n    if (typeof operator === \"string\" && operator in OPERATOR_DESCRIPTIONS) {\n        label = getDescr(operator, fieldDefType) || getOperatorDescription(operator, fieldDefType);\n    } else {\n        label = formatValue(operator);\n    }\n    if (negate) {\n        return _t(`not %(operator_label)s`, { operator_label: label });\n    }\n    return label;\n}\n\nfunction getOperatorInfo(operator, fieldDefType, negate = false) {\n    const key = toKey(operator, negate);\n    const label = getOperatorLabel(operator, fieldDefType, negate);\n    return [key, label];\n}\n\nexport function getOperatorEditorInfo(operators, fieldDef) {\n    const defaultOperator = operators[0];\n    const operatorsInfo = operators.map((operator) => getOperatorInfo(operator, fieldDef?.type));\n    return {\n        component: Select,\n        extractProps: ({ update, value: [operator, negate] }) => {\n            const [operatorKey, operatorLabel] = getOperatorInfo(operator, fieldDef?.type, negate);\n            const options = [...operatorsInfo];\n            if (!options.some(([key]) => key === operatorKey)) {\n                options.push([operatorKey, operatorLabel]);\n            }\n            return {\n                value: operatorKey,\n                update: (operatorKey) => update(...toOperator(operatorKey)),\n                options,\n            };\n        },\n        defaultValue: () => defaultOperator,\n        isSupported: ([operator]) =>\n            typeof operator === \"string\" && operator in OPERATOR_DESCRIPTIONS, // should depend on fieldDef too... (e.g. parent_id does not always make sense)\n        message: _t(\"Operator not supported\"),\n        stringify: ([operator, negate]) => getOperatorLabel(operator, fieldDef?.type, negate),\n    };\n}\n", "import { DateTimeInput } from \"@web/core/datetime/datetime_input\";\nimport { Domain } from \"@web/core/domain\";\nimport {\n    deserializeDate,\n    deserializeDateTime,\n    serializeDate,\n    serializeDateTime,\n} from \"@web/core/l10n/dates\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { connector, formatValue, isTree } from \"@web/core/tree_editor/condition_tree\";\nimport {\n    DomainSelectorAutocomplete,\n    DomainSelectorSingleAutocomplete,\n} from \"@web/core/tree_editor/tree_editor_autocomplete\";\nimport { Input, InRange, List, Range, Select } from \"@web/core/tree_editor/tree_editor_components\";\nimport { disambiguate, getResModel, isId } from \"@web/core/tree_editor/utils\";\nimport { unique } from \"@web/core/utils/arrays\";\n\nconst { DateTime } = luxon;\n\n// ============================================================================\n\nconst formatters = registry.category(\"formatters\");\nconst parsers = registry.category(\"parsers\");\n\nfunction parseValue(fieldType, value) {\n    const parser = parsers.get(fieldType, (value) => value);\n    try {\n        return parser(value);\n    } catch {\n        return value;\n    }\n}\n\nfunction isParsable(fieldType, value) {\n    const parser = parsers.get(fieldType, (value) => value);\n    try {\n        parser(value);\n    } catch {\n        return false;\n    }\n    return true;\n}\n\nfunction genericSerializeDate(type, value) {\n    return type === \"date\" ? serializeDate(value) : serializeDateTime(value);\n}\n\nfunction genericDeserializeDate(type, value) {\n    return type === \"date\" ? deserializeDate(value) : deserializeDateTime(value);\n}\n\nfunction placeholderForSelect(displayPlaceholder) {\n    if (displayPlaceholder) {\n        return _t(`Select one or several criteria`);\n    }\n}\n\nfunction placeholderForInput(displayPlaceholder) {\n    if (displayPlaceholder) {\n        return _t(`Press \"Enter\" to add criterion`);\n    }\n}\n\nconst STRING_EDITOR = {\n    component: Input,\n    extractProps: ({ value, update, displayPlaceholder }) => ({\n        value,\n        update,\n        placeholder: placeholderForInput(displayPlaceholder),\n    }),\n    isSupported: (value) => typeof value === \"string\",\n    defaultValue: () => \"\",\n};\n\nfunction makeSelectEditor(options, params = {}) {\n    const getOption = (value) => options.find(([v]) => v === value) || null;\n    return {\n        component: Select,\n        extractProps: ({ value, update, displayPlaceholder }) => ({\n            value,\n            update,\n            options,\n            addBlankOption: params.addBlankOption,\n            placeholder: placeholderForSelect(displayPlaceholder),\n        }),\n        isSupported: (value) => Boolean(getOption(value)),\n        defaultValue: () => options[0]?.[0] ?? false,\n        stringify: (value, disambiguate) => {\n            const option = getOption(value);\n            return option ? option[1] : disambiguate ? formatValue(value) : String(value);\n        },\n        message: _t(\"Value not in selection\"),\n    };\n}\n\nfunction getDomain(fieldDef) {\n    if (fieldDef.type === \"many2one\") {\n        return [];\n    }\n    try {\n        return new Domain(fieldDef.domain || []).toList();\n    } catch {\n        return [];\n    }\n}\n\nfunction makeAutoCompleteEditor(fieldDef) {\n    return {\n        component: DomainSelectorAutocomplete,\n        extractProps: ({ value, update }) => ({\n            resModel: getResModel(fieldDef),\n            fieldString: fieldDef.string,\n            domain: getDomain(fieldDef),\n            update: (value) => update(unique(value)),\n            resIds: unique(value),\n            placeholder: placeholderForSelect(true),\n        }),\n        isSupported: (value) => Array.isArray(value),\n        defaultValue: () => [],\n    };\n}\n\nfunction isLitteralObject(value) {\n    return typeof value === \"object\" && !Array.isArray(value) && value !== null;\n}\n\n// ============================================================================\n\nfunction getPartialValueEditorInfo(fieldDef, operator, params = {}) {\n    switch (operator) {\n        case \"set\":\n        case \"not set\":\n            return {\n                component: null,\n                extractProps: null,\n                isSupported: (value) =>\n                    value === false || (fieldDef.type === \"boolean\" && value === true),\n                defaultValue: () => false,\n            };\n        case \"=like\":\n        case \"=ilike\":\n        case \"like\":\n        case \"not like\":\n        case \"ilike\":\n        case \"not ilike\":\n            return STRING_EDITOR;\n        case \"between\": {\n            const editorInfo = getValueEditorInfo(fieldDef, \"=\", params);\n            const { defaultValue } = getValueEditorInfo(fieldDef, \"=\", {\n                ...params,\n                forBetween: true,\n            });\n            return {\n                component: Range,\n                extractProps: ({ value, update }) => ({\n                    value,\n                    update,\n                    editorInfo,\n                }),\n                isSupported: (value) => Array.isArray(value) && value.length === 2,\n                defaultValue: () => {\n                    const value = defaultValue();\n                    return isLitteralObject(value) ? [value.start, value.end] : [value, value];\n                },\n                shouldResetValue: (value) =>\n                    !editorInfo.isSupported(value[0]) || !editorInfo.isSupported(value[1]),\n            };\n        }\n        case \"in range\": {\n            return {\n                component: InRange,\n                extractProps: ({ value, update }) => ({\n                    value,\n                    update,\n                    valueTypeEditorInfo: makeSelectEditor(InRange.options, params),\n                    betweenEditorInfo: getValueEditorInfo(fieldDef, \"between\", params),\n                }),\n                isSupported: (value) =>\n                    Array.isArray(value) &&\n                    value.length === 4 &&\n                    value[0] === fieldDef.type &&\n                    InRange.options.some(([t]) => t === value[1]),\n                defaultValue: () => [fieldDef.type, \"today\", false, false],\n            };\n        }\n        case \"in\":\n        case \"not in\": {\n            switch (fieldDef.type) {\n                case \"tags\":\n                    return STRING_EDITOR;\n                case \"many2one\":\n                case \"many2many\":\n                case \"one2many\":\n                    return makeAutoCompleteEditor(fieldDef);\n                default: {\n                    const editorInfo = getValueEditorInfo(fieldDef, \"=\", {\n                        ...params,\n                        addBlankOption: true,\n                        startEmpty: true,\n                    });\n                    return {\n                        component: List,\n                        extractProps: ({ value, update }) => {\n                            if (!disambiguate(value)) {\n                                const { stringify } = editorInfo;\n                                editorInfo.stringify = (val) => stringify(val, false);\n                            }\n                            return {\n                                value,\n                                update,\n                                editorInfo,\n                            };\n                        },\n                        isSupported: (value) => Array.isArray(value),\n                        defaultValue: () => [],\n                        shouldResetValue: (value) => !value.every(editorInfo.isSupported),\n                    };\n                }\n            }\n        }\n        case \"any\":\n        case \"not any\": {\n            switch (fieldDef.type) {\n                case \"many2one\":\n                case \"many2many\":\n                case \"one2many\": {\n                    return {\n                        component: null,\n                        extractProps: null,\n                        isSupported: isTree,\n                        defaultValue: () => connector(\"&\"),\n                    };\n                }\n            }\n        }\n    }\n\n    const { type } = fieldDef;\n    switch (type) {\n        case \"integer\":\n        case \"float\":\n        case \"monetary\": {\n            const formatType = type === \"integer\" ? \"integer\" : \"float\";\n            return {\n                component: Input,\n                extractProps: ({ value, update, displayPlaceholder }) => ({\n                    value: String(value),\n                    update: (value) => update(parseValue(formatType, value)),\n                    startEmpty: params.startEmpty,\n                    placeholder: placeholderForInput(displayPlaceholder),\n                }),\n                isSupported: () => true,\n                defaultValue: () => 1,\n                shouldResetValue: (value) => parseValue(formatType, value) === value,\n            };\n        }\n        case \"date\":\n        case \"datetime\":\n            return {\n                component: DateTimeInput,\n                extractProps: ({ value, update, displayPlaceholder }) => ({\n                    value:\n                        params.startEmpty || value === false\n                            ? false\n                            : genericDeserializeDate(type, value),\n                    type,\n                    onApply: (value) => {\n                        if (!params.startEmpty || value) {\n                            update(\n                                genericSerializeDate(type, value || DateTime.local().startOf(\"day\"))\n                            );\n                        }\n                    },\n                    placeholder: placeholderForSelect(displayPlaceholder),\n                }),\n                isSupported: (value) => typeof value === \"string\" && isParsable(type, value),\n                defaultValue: (operator) => {\n                    const datetime = DateTime.local();\n                    if (operator === \">\") {\n                        return genericSerializeDate(type, datetime.endOf(\"day\"));\n                    }\n                    const start = genericSerializeDate(type, datetime.startOf(\"day\"));\n                    if (params.forBetween) {\n                        return { start, end: genericSerializeDate(type, datetime.endOf(\"day\")) };\n                    }\n                    return start;\n                },\n                shouldResetValue: () => true,\n                stringify: (value) => {\n                    if (value === false) {\n                        return _t(\"False\");\n                    }\n                    if (typeof value === \"string\" && isParsable(type, value)) {\n                        const formatter = formatters.get(type, formatValue);\n                        return formatter(genericDeserializeDate(type, value));\n                    }\n                    return formatValue(value);\n                },\n                message: _t(\"Not a valid %s\", type),\n            };\n        case \"char\":\n        case \"html\":\n        case \"text\":\n            return STRING_EDITOR;\n        case \"many2one\": {\n            if ([\"=\", \"!=\"].includes(operator)) {\n                return {\n                    component: DomainSelectorSingleAutocomplete,\n                    extractProps: ({ value, update }) => ({\n                        resModel: getResModel(fieldDef),\n                        fieldString: fieldDef.string,\n                        update,\n                        resId: value,\n                    }),\n                    isSupported: () => true,\n                    defaultValue: () => false,\n                    shouldResetValue: (value) => value !== false && !isId(value),\n                };\n            } else if ([\"parent_of\", \"child_of\"].includes(operator)) {\n                return makeAutoCompleteEditor(fieldDef);\n            }\n            break;\n        }\n        case \"many2many\":\n        case \"one2many\":\n            if ([\"=\", \"!=\"].includes(operator)) {\n                return makeAutoCompleteEditor(fieldDef);\n            }\n            break;\n        case \"selection\": {\n            const options = fieldDef.selection || [];\n            return makeSelectEditor(options, params);\n        }\n        case undefined: {\n            const options = [[1, \"1\"]];\n            return makeSelectEditor(options, params);\n        }\n    }\n\n    // Global default for visualization mainly. It is there to visualize what\n    // has been produced in the debug textarea (in o_domain_selector_debug_container)\n    // It is hardly useful to produce a string in general.\n    return {\n        component: Input,\n        extractProps: ({ value, update }) => ({\n            value: String(value),\n            update,\n        }),\n        isSupported: () => true,\n        defaultValue: () => \"\",\n    };\n}\n\nexport function getValueEditorInfo(fieldDef, operator, options = {}) {\n    const info = getPartialValueEditorInfo(fieldDef || {}, operator, options);\n    return {\n        extractProps: ({ value, update }) => ({ value, update }),\n        message: _t(\"Value not supported\"),\n        stringify: (val, disambiguate = true) => {\n            if (disambiguate) {\n                return formatValue(val);\n            }\n            return String(val);\n        },\n        ...info,\n    };\n}\n\nexport function getDefaultValue(fieldDef, operator, value = null) {\n    const { isSupported, shouldResetValue, defaultValue } = getValueEditorInfo(fieldDef, operator);\n    if (value === null || !isSupported(value) || shouldResetValue?.(value)) {\n        return defaultValue(operator);\n    }\n    return value;\n}\n", "import { constructTreeFromDomain } from \"./construct_tree_from_domain\";\nimport { introduceVirtualOperators } from \"./virtual_operators\";\n\nexport function treeFromDomain(domain, options = {}) {\n    const tree = constructTreeFromDomain(domain, options.distributeNot);\n    return introduceVirtualOperators(tree, options);\n}\n", "import { constructTreeFromExpression } from \"./construct_tree_from_expression\";\nimport { introduceVirtualOperators } from \"./virtual_operators\";\n\nexport function treeFromExpression(expression, options = {}) {\n    const tree = constructTreeFromExpression(expression, options);\n    return introduceVirtualOperators(tree, options);\n}\n", "import {\n    deserializeDate,\n    deserializeDateTime,\n    formatDate,\n    formatDateTime,\n} from \"@web/core/l10n/dates\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { getOperatorLabel } from \"@web/core/tree_editor/tree_editor_operator_editor\";\nimport { unique, zip } from \"@web/core/utils/arrays\";\nimport { condition, Expression, isTree, normalizeValue } from \"./condition_tree\";\nimport { constructTreeFromDomain } from \"./construct_tree_from_domain\";\nimport { disambiguate, getResModel, isId } from \"./utils\";\nimport { introduceVirtualOperators } from \"./virtual_operators\";\nimport { InRange } from \"./tree_editor_components\";\n\n/**\n * @param {import(\"@web/core/tree_editor/condition_tree\").Value} val\n * @param {boolean} disambiguate\n * @param {Object|null} fieldDef\n * @param {Object} displayNames\n * @returns\n */\nfunction formatValue(val, disambiguate, fieldDef, displayNames) {\n    if (val instanceof Expression) {\n        return val.toString();\n    }\n    if (displayNames && isId(val)) {\n        if (typeof displayNames[val] === \"string\") {\n            val = displayNames[val];\n        } else {\n            return _t(\"Inaccessible/missing record ID: %s\", val);\n        }\n    }\n    if (fieldDef?.type === \"selection\") {\n        const [, label] = (fieldDef.selection || []).find(([v]) => v === val) || [];\n        if (label !== undefined) {\n            val = label;\n        }\n    }\n    if (typeof val === \"string\") {\n        if (fieldDef?.type === \"datetime\") {\n            return formatDateTime(deserializeDateTime(val));\n        }\n        if (fieldDef?.type === \"date\") {\n            return formatDate(deserializeDate(val));\n        }\n    }\n    if (disambiguate && typeof val === \"string\") {\n        return JSON.stringify(val);\n    }\n    return val;\n}\n\nfunction getPathsInTree(tree, lookInSubTrees = false) {\n    const paths = [];\n    if (tree.type === \"condition\") {\n        paths.push(tree.path);\n        if (typeof tree.path === \"string\" && lookInSubTrees && isTree(tree.value)) {\n            const subTreePaths = getPathsInTree(tree.value, lookInSubTrees);\n            for (const p of subTreePaths) {\n                if (typeof p === \"string\") {\n                    paths.push(`${tree.path}.${p}`);\n                }\n            }\n        }\n    }\n    if (tree.type === \"connector\" && tree.children) {\n        for (const child of tree.children) {\n            paths.push(...getPathsInTree(child, lookInSubTrees));\n        }\n    }\n    return unique(paths);\n}\n\nfunction simplifyTree(tree) {\n    if (tree.type === \"condition\") {\n        return tree;\n    }\n    const processedChildren = tree.children.map(simplifyTree);\n    if (tree.value === \"&\") {\n        return { ...tree, children: processedChildren };\n    }\n    const children = [];\n    const childrenByPath = {};\n    for (let index = 0; index < processedChildren.length; index++) {\n        const child = processedChildren[index];\n        if (\n            child.type === \"connector\" ||\n            typeof child.path !== \"string\" ||\n            ![\"=\", \"in\"].includes(child.operator)\n        ) {\n            children.push(child);\n        } else {\n            if (!childrenByPath[child.path]) {\n                childrenByPath[child.path] = { elems: [], index };\n                children.push(child); // will be replaced if necessary\n            }\n            childrenByPath[child.path].elems.push(child);\n        }\n    }\n    for (const path in childrenByPath) {\n        if (childrenByPath[path].elems.length === 1) {\n            continue;\n        }\n        const value = [];\n        for (const child of childrenByPath[path].elems) {\n            if (child.operator === \"=\") {\n                value.push(child.value);\n            } else {\n                value.push(...child.value);\n            }\n        }\n        children[childrenByPath[path].index] = condition(path, \"in\", normalizeValue(unique(value)));\n    }\n    if (children.length === 1) {\n        return { ...children[0] };\n    }\n    return { ...tree, children };\n}\n\nfunction _extractIdsRecursive(tree, getFieldDef, idsByModel) {\n    if (tree.type === \"condition\") {\n        const fieldDef = getFieldDef(tree.path);\n        if ([\"many2one\", \"many2many\", \"one2many\"].includes(fieldDef?.type)) {\n            const value = tree.value;\n            const values = Array.isArray(value) ? value : [value];\n            const ids = values.filter((val) => isId(val));\n            const resModel = getResModel(fieldDef);\n            if (ids.length) {\n                if (!idsByModel[resModel]) {\n                    idsByModel[resModel] = [];\n                }\n                idsByModel[resModel].push(...ids);\n            }\n        }\n    }\n    if (tree.type === \"connector\") {\n        for (const child of tree.children) {\n            _extractIdsRecursive(child, getFieldDef, idsByModel);\n        }\n    }\n    return idsByModel;\n}\n\nfunction extractIdsFromTree(tree, getFieldDef) {\n    const idsByModel = _extractIdsRecursive(tree, getFieldDef, {});\n\n    for (const resModel in idsByModel) {\n        idsByModel[resModel] = unique(idsByModel[resModel]);\n    }\n\n    return idsByModel;\n}\n\nexport const treeProcessorService = {\n    dependencies: [\"field\", \"name\"],\n    async: [\n        \"getDomainTreeDescription\",\n        \"getDomainTreeTooltip\",\n        \"makeGetConditionDescription\",\n        \"makeGetFieldDef\",\n        \"treeFromDomain\",\n    ],\n    start(_, { field: fieldService, name: nameService }) {\n        async function getDisplayNames(tree, getFieldDef) {\n            const resIdsByModel = extractIdsFromTree(tree, getFieldDef);\n            const proms = [];\n            const resModels = [];\n            for (const [resModel, resIds] of Object.entries(resIdsByModel)) {\n                resModels.push(resModel);\n                proms.push(nameService.loadDisplayNames(resModel, resIds));\n            }\n            return Object.fromEntries(zip(resModels, await Promise.all(proms)));\n        }\n\n        async function makeGetPathDescriptions(resModel, tree, limit) {\n            const paths = getPathsInTree(tree);\n            const promises = [];\n            const pathDescriptions = new Map();\n            for (const path of paths) {\n                promises.push(\n                    fieldService.loadPathDescription(resModel, path).then(({ displayNames }) => {\n                        pathDescriptions.set(\n                            path,\n                            `${displayNames.slice(0, limit).join(\" \\u2794 \")}${\n                                displayNames.length > limit ? \"...\" : \"\"\n                            }`\n                        );\n                    })\n                );\n            }\n            await Promise.all(promises);\n            return (path) => pathDescriptions.get(path);\n        }\n\n        async function makeGetConditionDescription(resModel, tree, limit, pathLimit) {\n            tree = simplifyTree(tree);\n            const [getFieldDef, getPathDescription] = await Promise.all([\n                makeGetFieldDef(resModel, tree),\n                makeGetPathDescriptions(resModel, tree, pathLimit),\n            ]);\n            const displayNames = await getDisplayNames(tree, getFieldDef);\n            return (node) =>\n                _getConditionDescription(\n                    node,\n                    getFieldDef,\n                    getPathDescription,\n                    displayNames,\n                    limit\n                );\n        }\n\n        function _getConditionDescription(\n            node,\n            getFieldDef,\n            getPathDescription,\n            displayNames,\n            limit = 5\n        ) {\n            let { operator, negate, value, path } = node;\n            if (operator === \"in range\" && value[1] === \"custom range\") {\n                operator = \"between\";\n                value = value.slice(2);\n            }\n            if ([\"=\", \"!=\"].includes(operator) && value === false) {\n                operator = operator === \"=\" ? \"not set\" : \"set\";\n            }\n            const fieldDef = getFieldDef(path);\n            const operatorLabel = getOperatorLabel(operator, fieldDef?.type, negate, (operator) => {\n                switch (operator) {\n                    case \"=\":\n                    case \"in\":\n                        return \"=\";\n                    case \"!=\":\n                    case \"not in\":\n                        return _t(\"not =\");\n                    case \"any\":\n                        return \":\";\n                    case \"not any\":\n                        return _t(\": not\");\n                }\n            });\n\n            const pathDescription = getPathDescription(path);\n            const description = {\n                pathDescription,\n                operatorDescription: operatorLabel,\n                valueDescription: null,\n            };\n\n            if (isTree(node.value)) {\n                return description;\n            }\n            if ([\"set\", \"not set\"].includes(operator)) {\n                return description;\n            }\n\n            const coModeldisplayNames = displayNames[getResModel(fieldDef)];\n            const dis = disambiguate(value, coModeldisplayNames);\n            let values;\n            if (operator === \"in range\") {\n                const valueType = value[1];\n                values = [InRange.options.find(([t]) => t === valueType)[1].toString()];\n            } else {\n                values = (Array.isArray(value) ? value : [value])\n                    .slice(0, limit)\n                    .map((val, index) =>\n                        index < limit - 1\n                            ? formatValue(val, dis, fieldDef, coModeldisplayNames)\n                            : \"...\"\n                    );\n            }\n\n            let join;\n            let addParenthesis = Array.isArray(value);\n            switch (operator) {\n                case \"between\":\n                    join = _t(\"and\");\n                    addParenthesis = false;\n                    break;\n                case \"in range\":\n                    join = _t(\" \");\n                    addParenthesis = false;\n                    break;\n                case \"in\":\n                case \"not in\":\n                    addParenthesis = values.length === 0;\n                // eslint-disable-next-line no-fallthrough\n                default:\n                    join = _t(\"or\");\n            }\n            description.valueDescription = { values, join, addParenthesis };\n            return description;\n        }\n\n        async function getDomainTreeDescription(\n            resModel,\n            tree,\n            isSubExpression = false,\n            limit = undefined,\n            pathLimit = undefined\n        ) {\n            tree = simplifyTree(tree);\n            if (tree.type === \"connector\") {\n                // we assume that the domain tree is normalized (--> there is at least two children)\n                const childDescriptions = tree.children.map((node) =>\n                    getDomainTreeDescription(resModel, node, true)\n                );\n                const separator = tree.value === \"&\" ? _t(\"and\") : _t(\"or\");\n                let description = await Promise.all(childDescriptions);\n                description = description.join(` ${separator} `);\n                if (isSubExpression || tree.negate) {\n                    description = `( ${description} )`;\n                }\n                if (tree.negate) {\n                    description = `! ${description}`;\n                }\n                return description;\n            }\n            const getFieldDef = await makeGetFieldDef(resModel, tree);\n            const getConditionDescription = await makeGetConditionDescription(\n                resModel,\n                tree,\n                limit,\n                pathLimit\n            );\n            const { pathDescription, operatorDescription, valueDescription } =\n                getConditionDescription(tree);\n            const stringDescription = [pathDescription, operatorDescription];\n            if (valueDescription) {\n                const { values, join, addParenthesis } = valueDescription;\n                const jointedValues = values.join(` ${join} `);\n                stringDescription.push(addParenthesis ? `( ${jointedValues} )` : jointedValues);\n            } else if (isTree(tree.value)) {\n                const _fieldDef = getFieldDef(tree.path);\n                const _resModel = getResModel(_fieldDef);\n                const _tree = tree.value;\n                const description = await getDomainTreeDescription(_resModel, _tree);\n                stringDescription.push(`( ${description} )`);\n            }\n            return stringDescription.join(\" \");\n        }\n\n        async function getTooltipLines(resModel, tree, depth = 0) {\n            const tabs = \" \".repeat(depth * 4);\n            tree = simplifyTree(tree);\n            if (tree.type === \"connector\") {\n                // we assume that the domain tree is normalized (--> there is at least two children)\n                let connector = tree.value === \"&\" ? _t(\"all\") : _t(\"any\");\n                if (tree.negate) {\n                    connector = tree.value === \"&\" ? _t(\"not all\") : _t(\"none\");\n                }\n                connector = `${tabs}${connector}`;\n                const childrenTooltipLines = await Promise.all(\n                    tree.children.map((node) => getTooltipLines(resModel, node, depth + 1))\n                );\n                return [connector, ...childrenTooltipLines].flat();\n            }\n            const getFieldDef = await makeGetFieldDef(resModel, tree);\n            const getConditionDescription = await makeGetConditionDescription(resModel, tree, 20);\n            const { pathDescription, operatorDescription, valueDescription } =\n                getConditionDescription(tree);\n            const descr = [];\n            const stringDescriptions = [pathDescription, operatorDescription];\n            if (valueDescription) {\n                const { values, join, addParenthesis } = valueDescription;\n                const jointedValues = values.join(` ${join} `);\n                stringDescriptions.push(addParenthesis ? `( ${jointedValues} )` : jointedValues);\n            }\n            descr.push(`${tabs}${stringDescriptions.join(\" \")}`);\n            if (isTree(tree.value)) {\n                const _fieldDef = getFieldDef(tree.path);\n                const _resModel = getResModel(_fieldDef);\n                const _tree = tree.value;\n                const tooltipLines = await getTooltipLines(_resModel, _tree, depth + 1);\n                descr.push(...tooltipLines);\n            }\n            return descr;\n        }\n\n        async function getDomainTreeTooltip(resModel, tree) {\n            const descriptions = await getTooltipLines(resModel, tree);\n            return descriptions.join(\"\\n\");\n        }\n\n        async function makeGetFieldDef(resModel, tree) {\n            const paths = new Set(getPathsInTree(tree, true));\n            const promises = [];\n            const fieldDefs = {};\n            for (const path of paths) {\n                promises.push(\n                    fieldService.loadFieldInfo(resModel, path).then(({ fieldDef }) => {\n                        fieldDefs[path] = fieldDef;\n                    })\n                );\n            }\n            await Promise.all(promises);\n            return (path) => {\n                if (typeof path === \"string\") {\n                    return fieldDefs[path];\n                }\n                return null;\n            };\n        }\n\n        async function treeFromDomain(resModel, domain, distributeNot = true) {\n            const tree = constructTreeFromDomain(domain, distributeNot);\n            const getFieldDef = await makeGetFieldDef(resModel, tree);\n            return introduceVirtualOperators(tree, { getFieldDef });\n        }\n\n        return {\n            getDomainTreeDescription,\n            getDomainTreeTooltip,\n            makeGetConditionDescription,\n            makeGetFieldDef,\n            treeFromDomain,\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"tree_processor\", treeProcessorService);\n", "export function disambiguate(value, displayNames) {\n    if (!Array.isArray(value)) {\n        return value === \"\";\n    }\n    let hasSomeString = false;\n    let hasSomethingElse = false;\n    for (const val of value) {\n        if (val === \"\") {\n            return true;\n        }\n        if (typeof val === \"string\" || (displayNames && isId(val))) {\n            hasSomeString = true;\n        } else {\n            hasSomethingElse = true;\n        }\n    }\n    return hasSomeString && hasSomethingElse;\n}\n\nexport function isId(value) {\n    return Number.isInteger(value) && value >= 1;\n}\n\nexport function getResModel(fieldDef) {\n    if (fieldDef) {\n        return fieldDef.is_property ? fieldDef.comodel : fieldDef.relation;\n    }\n    return null;\n}\n\nconst SPECIAL_FIELDS = [\"country_id\", \"user_id\", \"partner_id\", \"stage_id\", \"id\"];\n\nexport function getDefaultPath(fieldDefs) {\n    for (const name of SPECIAL_FIELDS) {\n        const fieldDef = fieldDefs[name];\n        if (fieldDef) {\n            return fieldDef.name;\n        }\n    }\n    const name = Object.keys(fieldDefs)[0];\n    if (name) {\n        return name;\n    }\n    throw new Error(`No field found`);\n}\n", "import {\n    applyTransformations,\n    areEqualTrees,\n    cloneTree,\n    condition,\n    connector,\n    expression,\n    FALSE_TREE,\n    isTree,\n    normalizeValue,\n    operate,\n    rewriteNConsecutiveChildren,\n    TRUE_TREE,\n} from \"./condition_tree\";\n\nfunction splitPath(path) {\n    const pathParts = typeof path === \"string\" ? path.split(\".\") : [];\n    const lastPart = pathParts.pop() || \"\";\n    const initialPath = pathParts.join(\".\");\n    return { initialPath, lastPart };\n}\n\nfunction isSimplePath(path) {\n    return typeof path === \"string\" && !splitPath(path).initialPath;\n}\n\nfunction wrapInAny(tree, initialPath, negate) {\n    let con = cloneTree(tree);\n    if (initialPath) {\n        con = condition(initialPath, \"any\", con);\n    }\n    con.negate = negate;\n    return con;\n}\n\nfunction introduceSetOperators(tree, options = {}) {\n    function _introduceSetOperator(c, options = {}) {\n        const { negate, path, operator, value } = c;\n        const fieldType = options.getFieldDef?.(path)?.type;\n        if ([\"=\", \"!=\"].includes(operator)) {\n            if (fieldType) {\n                if (fieldType === \"boolean\" && value === true) {\n                    return condition(path, operator === \"=\" ? \"set\" : \"not set\", value, negate);\n                } else if (\n                    ![\"many2one\", \"date\", \"datetime\"].includes(fieldType) &&\n                    value === false\n                ) {\n                    return condition(path, operator === \"=\" ? \"not set\" : \"set\", value, negate);\n                }\n            }\n        }\n    }\n    return operate(_introduceSetOperator, tree, options);\n}\n\nfunction eliminateSetOperators(tree) {\n    function _removeSetOperator(c) {\n        const { negate, path, operator, value } = c;\n        if ([\"set\", \"not set\"].includes(operator)) {\n            if (value === true) {\n                return condition(path, operator === \"set\" ? \"=\" : \"!=\", value, negate);\n            }\n            return condition(path, operator === \"set\" ? \"!=\" : \"=\", value, negate);\n        }\n    }\n    return operate(_removeSetOperator, tree);\n}\n\nfunction introduceStartsWithOperators(tree, options) {\n    function _introduceStartsWithOperator(c, options) {\n        const { negate, path, operator, value } = c;\n        const fieldType = options.getFieldDef?.(path)?.type;\n        if (\n            [\"char\", \"text\", \"html\"].includes(fieldType) &&\n            operator === \"=ilike\" &&\n            typeof value === \"string\"\n        ) {\n            if (value.endsWith(\"%\")) {\n                return condition(path, \"starts with\", value.slice(0, -1), negate);\n            }\n        }\n    }\n    return operate(_introduceStartsWithOperator, tree, options);\n}\n\nfunction eliminateStartsWithOperators(tree) {\n    function _eliminateStartsWithOperator(c) {\n        const { negate, path, operator, value } = c;\n        if (operator === \"starts with\") {\n            return condition(path, \"=ilike\", `${value}%`, negate);\n        }\n    }\n    return operate(_eliminateStartsWithOperator, tree);\n}\n\nfunction isSimpleAnd(c) {\n    if (\n        c.type === \"connector\" &&\n        c.value === \"&\" &&\n        !c.negate &&\n        c.children.length === 2 &&\n        c.children.every((child) => child.type === \"condition\" && !child.negate)\n    ) {\n        return true;\n    }\n    return false;\n}\n\nfunction isBetween(c) {\n    if (isSimpleAnd(c)) {\n        const [\n            { path: p1, operator: op1, value: value1 },\n            { path: p2, operator: op2, value: value2 },\n        ] = c.children;\n        if (p1 === p2 && op1 === \">=\" && op2 === \"<=\") {\n            return { path: p1, value1, value2 };\n        }\n    }\n    return false;\n}\n\nfunction makeBetween(path, value1, value2) {\n    return connector(\"&\", [condition(path, \">=\", value1), condition(path, \"<=\", value2)]);\n}\n\nfunction isStrictBetween(c) {\n    if (isSimpleAnd(c)) {\n        const [\n            { path: p1, operator: op1, value: value1 },\n            { path: p2, operator: op2, value: value2 },\n        ] = c.children;\n        if (p1 === p2 && op1 === \">=\" && op2 === \"<\") {\n            return { path: p1, value1, value2 };\n        }\n    }\n    return false;\n}\n\nfunction makeStrictBetween(path, value1, value2) {\n    return connector(\"&\", [condition(path, \">=\", value1), condition(path, \"<\", value2)]);\n}\n\nfunction boundDate(delta) {\n    if (!delta) {\n        return expression(`context_today().strftime(\"%Y-%m-%d\")`);\n    }\n    return expression(`(context_today() + relativedelta(${delta})).strftime('%Y-%m-%d')`);\n}\n\nfunction boundDatetime(delta) {\n    if (!delta) {\n        return expression(\n            `datetime.datetime.combine(context_today(), datetime.time(0, 0, 0)).to_utc().strftime(\"%Y-%m-%d %H:%M:%S\")`\n        );\n    }\n    return expression(\n        `datetime.datetime.combine(context_today() + relativedelta(${delta}), datetime.time(0, 0, 0)).to_utc().strftime(\"%Y-%m-%d %H:%M:%S\")`\n    );\n}\n\nconst BOUNDS_SMART_DATES = [\n    [\"today\", \"today\", \"today +1d\"],\n    [\"last 7 days\", \"today -7d\", \"today\"],\n    [\"last 30 days\", \"today -30d\", \"today\"],\n    [\"month to date\", \"today =1d\", \"today +1d\"],\n    [\"last month\", \"today =1d -1m\", \"today =1d\"],\n    [\"year to date\", \"today =1m =1d\", \"today +1d\"],\n    [\"last 12 months\", \"today =1d -12m\", \"today =1d\"],\n];\nconst DELTAS = [\n    [\"today\", \"\", \"days = 1\"],\n    [\"last 7 days\", \"days = -7\", \"\"],\n    [\"last 30 days\", \"days = -30\", \"\"],\n    [\"month to date\", \"day = 1\", \"days = 1\"],\n    [\"last month\", \"day = 1, months = -1\", \"day = 1\"],\n    [\"year to date\", \"day = 1, month = 1\", \"days = 1\"],\n    [\"last 12 months\", \"day = 1, months = -12\", \"day = 1\"],\n];\nconst BOUNDS_DATE = DELTAS.map(([k, l, r]) => [k, boundDate(l), boundDate(r)]);\nconst BOUNDS_DATETIME = DELTAS.map(([k, l, r]) => [k, boundDatetime(l), boundDatetime(r)]);\n\nfunction getBounds(generateSmartDates, fieldType) {\n    return generateSmartDates\n        ? BOUNDS_SMART_DATES\n        : fieldType === \"date\"\n        ? BOUNDS_DATE\n        : BOUNDS_DATETIME;\n}\n\nfunction introduceInRangeOperators(tree, options = {}) {\n    function _introduceInRangeOperator(c, options) {\n        const res1 = isStrictBetween(c);\n        if (res1) {\n            const generateSmartDates =\n                \"generateSmartDates\" in options ? options.generateSmartDates : true;\n            // @ts-ignore\n            const { path, value1, value2 } = res1;\n            const fieldType = options.getFieldDef?.(path)?.type;\n            if ([\"date\", \"datetime\"].includes(fieldType) && isSimplePath(path)) {\n                const bounds = getBounds(generateSmartDates, fieldType);\n                for (const [valueType, leftBound, rightBound] of bounds) {\n                    if (\n                        generateSmartDates\n                            ? value1 === leftBound && value2 === rightBound\n                            : value1._expr === leftBound._expr && value2._expr === rightBound._expr\n                    ) {\n                        return condition(path, \"in range\", [fieldType, valueType, false, false]);\n                    }\n                }\n            }\n        }\n        const res2 = isBetween(c);\n        if (res2) {\n            // @ts-ignore\n            const { path, value1, value2 } = res2;\n            const fieldType = options.getFieldDef?.(path)?.type;\n            if ([\"date\", \"datetime\"].includes(fieldType) && isSimplePath(path)) {\n                return condition(path, \"in range\", [\n                    fieldType,\n                    \"custom range\",\n                    // @ts-ignore\n                    ...normalizeValue([value1, value2]),\n                ]);\n            }\n        }\n    }\n    return operate(\n        rewriteNConsecutiveChildren(_introduceInRangeOperator),\n        tree,\n        options,\n        \"connector\"\n    );\n}\n\nfunction eliminateInRangeOperators(tree, options = {}) {\n    function _eliminateInRangeOperator(c, options) {\n        const { negate, path, operator, value } = c;\n        // @ts-ignore\n        if (operator !== \"in range\") {\n            return;\n        }\n        const { initialPath, lastPart } = splitPath(path);\n        const [fieldType, valueType, value1, value2] = value;\n        let tree;\n        if (valueType === \"custom range\") {\n            tree = makeBetween(lastPart, value1, value2);\n        } else {\n            const generateSmartDates =\n                \"generateSmartDates\" in options ? options.generateSmartDates : true;\n            const bounds = getBounds(generateSmartDates, fieldType);\n            const [, leftBound, rightBound] = bounds.find(([v]) => v === valueType);\n            tree = makeStrictBetween(lastPart, leftBound, rightBound);\n        }\n        return wrapInAny(tree, initialPath, negate);\n    }\n    return operate(_eliminateInRangeOperator, tree, options);\n}\n\nfunction introduceBetweenOperators(tree, options = {}) {\n    function _introduceBetweenOperator(c, options) {\n        const res = isBetween(c);\n        if (!res) {\n            return;\n        }\n        // @ts-ignore\n        const { path, value1, value2 } = res;\n        const fieldType = options.getFieldDef?.(path)?.type;\n        if ([\"integer\", \"float\", \"monetary\"].includes(fieldType) && isSimplePath(path)) {\n            return condition(path, \"between\", normalizeValue([value1, value2]));\n        }\n    }\n    return operate(\n        rewriteNConsecutiveChildren(_introduceBetweenOperator),\n        tree,\n        options,\n        \"connector\"\n    );\n}\n\nfunction eliminateBetweenOperators(tree) {\n    function _eliminateBetweenOperator(c) {\n        const { negate, path, operator, value } = c;\n        // @ts-ignore\n        if (operator !== \"between\") {\n            return;\n        }\n        const { initialPath, lastPart } = splitPath(path);\n        return wrapInAny(makeBetween(lastPart, value[0], value[1]), initialPath, negate);\n    }\n    return operate(_eliminateBetweenOperator, tree);\n}\n\nfunction _eliminateAnyOperator(c) {\n    const { path, operator, value, negate } = c;\n    if (\n        operator === \"any\" &&\n        isTree(value) &&\n        value.type === \"condition\" &&\n        typeof path === \"string\" &&\n        typeof value.path === \"string\" &&\n        !negate &&\n        !value.negate &&\n        [\"between\", \"in range\"].includes(value.operator)\n    ) {\n        return condition(`${path}.${value.path}`, value.operator, value.value);\n    }\n}\n\nfunction eliminateAnyOperators(tree) {\n    return operate(_eliminateAnyOperator, tree);\n}\n\nfunction removeFalseTrueLeaves(tree) {\n    function _removeFalseTrueLeave(c) {\n        const { path, operator, value, negate } = c;\n        if (areEqualTrees(condition(path, operator, value), FALSE_TREE)) {\n            return connector(negate ? \"&\" : \"|\", []);\n        }\n        if (areEqualTrees(condition(path, operator, value), TRUE_TREE)) {\n            return connector(negate ? \"|\" : \"&\", []);\n        }\n    }\n    return operate(_removeFalseTrueLeave, tree);\n}\n\nexport function introduceVirtualOperators(tree, options = {}) {\n    return applyTransformations(\n        [\n            eliminateAnyOperators,\n            introduceSetOperators,\n            introduceStartsWithOperators,\n            introduceBetweenOperators,\n            introduceInRangeOperators,\n        ],\n        tree,\n        options\n    );\n}\n\nexport function eliminateVirtualOperators(tree, options = {}) {\n    return applyTransformations(\n        [\n            eliminateInRangeOperators,\n            eliminateBetweenOperators,\n            eliminateStartsWithOperators,\n            eliminateSetOperators,\n        ],\n        tree,\n        options\n    );\n}\n\nexport function areEquivalentTrees(tree, otherTree) {\n    const simplifiedTree = removeFalseTrueLeaves(eliminateVirtualOperators(tree));\n    const otherSimplifiedTree = removeFalseTrueLeaves(eliminateVirtualOperators(otherTree));\n    return areEqualTrees(simplifiedTree, otherSimplifiedTree);\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { browser } from \"@web/core/browser/browser\";\n\nimport { EventBus, Component, useState } from \"@odoo/owl\";\n\nexport class BlockUI extends Component {\n    static props = {\n        bus: EventBus,\n    };\n\n    static template = \"web.BlockUI\";\n\n    setup() {\n        this.messagesByDuration = [\n            { time: 20, l1: _t(\"Loading...\") },\n            { time: 40, l1: _t(\"Still loading...\") },\n            {\n                time: 60,\n                l1: _t(\"Still loading...\"),\n                l2: _t(\"Please be patient.\"),\n            },\n            {\n                time: 180,\n                l1: _t(\"Don't leave yet,\"),\n                l2: _t(\"it's still loading...\"),\n            },\n            {\n                time: 120,\n                l1: _t(\"You may not believe it,\"),\n                l2: _t(\"but the application is actually loading...\"),\n            },\n            {\n                time: 3180,\n                l1: _t(\"Take a minute to get a coffee,\"),\n                l2: _t(\"because it's loading...\"),\n            },\n            {\n                time: null,\n                l1: _t(\"Maybe you should consider reloading the application by pressing F5...\"),\n            },\n        ];\n        this.BLOCK_STATES = { UNBLOCKED: 0, BLOCKED: 1, VISIBLY_BLOCKED: 2 };\n        this.state = useState({\n            blockState: this.BLOCK_STATES.UNBLOCKED,\n            line1: \"\",\n            line2: \"\",\n        });\n\n        this.props.bus.addEventListener(\"BLOCK\", this.block.bind(this));\n        this.props.bus.addEventListener(\"UNBLOCK\", this.unblock.bind(this));\n    }\n\n    replaceMessage(index) {\n        const message = this.messagesByDuration[index];\n        this.state.line1 = message.l1;\n        this.state.line2 = message.l2 || \"\";\n        if (message.time !== null) {\n            this.msgTimer = browser.setTimeout(() => {\n                this.replaceMessage(index + 1);\n            }, message.time * 1000);\n        }\n    }\n\n    block(ev) {\n        const showBlockedUI = () => (this.state.blockState = this.BLOCK_STATES.VISIBLY_BLOCKED);\n        const delay = ev.detail?.delay;\n        if (delay) {\n            this.state.blockState = this.BLOCK_STATES.BLOCKED;\n            this.showBlockedUITimer = setTimeout(showBlockedUI, delay);\n        } else {\n            showBlockedUI();\n        }\n\n        if (ev.detail?.message) {\n            this.state.line1 = ev.detail.message;\n        } else {\n            this.replaceMessage(0);\n        }\n    }\n\n    unblock() {\n        this.state.blockState = this.BLOCK_STATES.UNBLOCKED;\n        clearTimeout(this.showBlockedUITimer);\n        clearTimeout(this.msgTimer);\n        this.state.line1 = \"\";\n        this.state.line2 = \"\";\n    }\n}\n", "import { useService } from \"@web/core/utils/hooks\";\nimport { registry } from \"@web/core/registry\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { BlockUI } from \"./block_ui\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { getTabableElements, isFocusable } from \"@web/core/utils/ui\";\nimport { getActiveHotkey } from \"../hotkeys/hotkey_service\";\n\nimport { EventBus, reactive, useEffect, useRef } from \"@odoo/owl\";\n\nexport const SIZES = { XS: 0, SM: 1, MD: 2, LG: 3, XL: 4, XXL: 5 };\n\nexport function getFirstAndLastTabableElements(el) {\n    const tabableEls = getTabableElements(el);\n    return [tabableEls[0], tabableEls[tabableEls.length - 1]];\n}\n\n/**\n * This hook will set the UI active element\n * when the caller component will mount/patch and\n * only if the t-reffed element has some tabable elements\n * or is itself focusable.\n *\n * The caller component could pass a `t-ref` value of its template\n * to delegate the UI active element to another element than itself.\n *\n * @param {string} refName\n */\nexport function useActiveElement(refName) {\n    if (!refName) {\n        throw new Error(\"refName not given to useActiveElement\");\n    }\n    const uiService = useService(\"ui\");\n    const ref = useRef(refName);\n\n    function trapFocus(e) {\n        const hotkey = getActiveHotkey(e);\n        if (![\"tab\", \"shift+tab\"].includes(hotkey)) {\n            return;\n        }\n        const el = e.currentTarget;\n        const [firstTabableEl, lastTabableEl] = getFirstAndLastTabableElements(el);\n        if (!firstTabableEl && !lastTabableEl) {\n            e.preventDefault();\n            e.stopPropagation();\n            return;\n        }\n        switch (hotkey) {\n            case \"tab\":\n                if (document.activeElement === lastTabableEl) {\n                    firstTabableEl.focus();\n                    e.preventDefault();\n                    e.stopPropagation();\n                }\n                break;\n            case \"shift+tab\":\n                if (document.activeElement === firstTabableEl) {\n                    lastTabableEl.focus();\n                    e.preventDefault();\n                    e.stopPropagation();\n                }\n                break;\n        }\n    }\n\n    useEffect(\n        (el) => {\n            if (el) {\n                const [firstTabableEl] = getFirstAndLastTabableElements(el);\n                if (!firstTabableEl && !isFocusable(el)) {\n                    // no tabable elements: no need to trap focus nor become the UI active element\n                    return;\n                }\n                const oldActiveElement = document.activeElement;\n                uiService.activateElement(el);\n\n                el.addEventListener(\"keydown\", trapFocus);\n\n                if (firstTabableEl) {\n                    if (!el.contains(document.activeElement)) {\n                        firstTabableEl.focus();\n                    }\n                } else if (el !== document.activeElement) {\n                    el.focus();\n                }\n                return async () => {\n                    // Components are destroyed from top to bottom, meaning that this cleanup is\n                    // called before the ones of children. As a consequence, event handlers added on\n                    // the current active element in children aren't removed yet, and can thus be\n                    // executed if we deactivate that active element right away (e.g. the blur and\n                    // change events could be triggered). For that reason, we wait for a micro-tick.\n                    await Promise.resolve();\n                    uiService.deactivateElement(el);\n                    el.removeEventListener(\"keydown\", trapFocus);\n\n                    /**\n                     * In some cases, the current active element is not\n                     * anymore in el (e.g. with ConfirmationDialog, the\n                     * confirm button is disabled when clicked, so the\n                     * focus is lost). In that case, we also want to restore\n                     * the focus to the previous active element so we\n                     * check if the current active element is the body\n                     */\n                    if (\n                        el.contains(document.activeElement) ||\n                        document.activeElement === document.body\n                    ) {\n                        oldActiveElement.focus();\n                    }\n                };\n            }\n        },\n        () => [ref.el]\n    );\n}\n\n// window size handling\nexport const MEDIAS_BREAKPOINTS = [\n    { maxWidth: 575 },\n    { minWidth: 576, maxWidth: 767 },\n    { minWidth: 768, maxWidth: 991 },\n    { minWidth: 992, maxWidth: 1199 },\n    { minWidth: 1200, maxWidth: 1399 },\n    { minWidth: 1400 },\n];\n\n/**\n * Create the MediaQueryList used both by the uiService and config from\n * `MEDIA_BREAKPOINTS`.\n *\n * @returns {MediaQueryList[]}\n */\nexport function getMediaQueryLists() {\n    return MEDIAS_BREAKPOINTS.map(({ minWidth, maxWidth }) => {\n        if (!maxWidth) {\n            return window.matchMedia(`(min-width: ${minWidth}px)`);\n        }\n        if (!minWidth) {\n            return window.matchMedia(`(max-width: ${maxWidth}px)`);\n        }\n        return window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`);\n    });\n}\n\n// window size handling.\nconst MEDIAS = getMediaQueryLists();\n\nexport const utils = {\n    getSize() {\n        return MEDIAS.findIndex((media) => media.matches);\n    },\n    isSmall(ui = {}) {\n        return (ui.size || utils.getSize()) <= SIZES.SM;\n    },\n};\n\nconst bus = new EventBus();\n\nexport function listenSizeChange(callback) {\n    bus.addEventListener(\"resize\", callback);\n    return () => bus.removeEventListener(\"resize\", callback);\n}\n\nexport const uiService = {\n    start(env) {\n        // block/unblock code\n        registry.category(\"main_components\").add(\"BlockUI\", { Component: BlockUI, props: { bus } });\n\n        let blockCount = 0;\n        function block(data) {\n            blockCount++;\n            // TODO could probably be improved to handle multiple block demands\n            // but that have different messages and delays\n            if (blockCount === 1) {\n                bus.trigger(\"BLOCK\", {\n                    message: data?.message,\n                    delay: data?.delay,\n                });\n            }\n        }\n        function unblock() {\n            blockCount--;\n            if (blockCount < 0) {\n                console.warn(\n                    \"Unblock ui was called more times than block, you should only unblock the UI if you have previously blocked it.\"\n                );\n                blockCount = 0;\n            }\n            if (blockCount === 0) {\n                bus.trigger(\"UNBLOCK\");\n            }\n        }\n\n        // UI active element code\n        let activeElems = [document];\n\n        function activateElement(el) {\n            activeElems.push(el);\n            bus.trigger(\"active-element-changed\", el);\n        }\n        function deactivateElement(el) {\n            activeElems = activeElems.filter((x) => x !== el);\n            bus.trigger(\"active-element-changed\", ui.activeElement);\n        }\n        function getActiveElementOf(el) {\n            for (const activeElement of [...activeElems].reverse()) {\n                if (activeElement.contains(el)) {\n                    return activeElement;\n                }\n            }\n        }\n\n        const ui = reactive({\n            bus,\n            size: utils.getSize(),\n            get activeElement() {\n                return activeElems[activeElems.length - 1];\n            },\n            get isBlocked() {\n                return blockCount > 0;\n            },\n            isSmall: utils.isSmall(),\n            block,\n            unblock,\n            activateElement,\n            deactivateElement,\n            getActiveElementOf,\n        });\n\n        // listen to media query status changes\n        const updateSize = () => {\n            const prevSize = ui.size;\n            ui.size = utils.getSize();\n            if (ui.size !== prevSize) {\n                ui.isSmall = utils.isSmall(ui);\n                bus.trigger(\"resize\");\n            }\n        };\n        browser.addEventListener(\"resize\", throttleForAnimation(updateSize));\n\n        Object.defineProperty(env, \"isSmall\", {\n            get() {\n                return ui.isSmall;\n            },\n        });\n\n        return ui;\n    },\n};\n\nregistry.category(\"services\").add(\"ui\", uiService);\n", "import { browser } from \"@web/core/browser/browser\";\nimport { pyToJsLocale } from \"@web/core/l10n/utils/locales\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { Cache } from \"@web/core/utils/cache\";\nimport { session } from \"@web/session\";\nimport { ensureArray, sortBy } from \"./utils/arrays\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { EventBus } from \"@odoo/owl\";\n\n// This file exports an object containing user-related information and functions\n// allowing to obtain/alter user-related information from the server.\n\nexport const userBus = new EventBus();\n\nfunction getCookieCompanyIds() {\n    if (cookie.get(\"cids\")) {\n        const cids = cookie.get(\"cids\");\n        if (typeof cids === \"string\") {\n            return cids.split(\"-\").map(Number);\n        }\n        if (typeof cids === \"number\") {\n            return [cids];\n        }\n    }\n    return [];\n}\n\n/**\n * This function exists for testing purposes. We don't want tests to share the\n * same cache. It allows to generate new caches at the beginning of tests.\n *\n * Note: with hoot, this will no longer be necessary.\n *\n * @returns Object\n */\nexport function _makeUser(session) {\n    // Retrieve user-related information from the session\n    const {\n        home_action_id: homeActionId,\n        is_admin: isAdmin,\n        is_internal_user: isInternalUser,\n        is_system: isSystem,\n        is_public: isPublic,\n        name,\n        partner_id: partnerId,\n        show_effect: showEffect,\n        uid: userId,\n        username: login,\n        user_context: context,\n        user_settings,\n        partner_write_date: writeDate,\n        user_companies: userCompanies,\n        groups = {},\n    } = session;\n    const settings = user_settings || {};\n\n    function updateActiveCompanies(cids, allowedCompanies, defaultCompanyId) {\n        activeCompanies = [];\n        cids.forEach((cid) => {\n            activeCompanies.push(allowedCompanies.find((c) => c.id === cid));\n        });\n        if (\n            activeCompanies.length === 0 ||\n            activeCompanies.length !== activeCompanies.filter(Boolean).length\n        ) {\n            activeCompanies = [defaultCompanyId];\n        }\n        // Sort companies, except for the first one which has a different status, as the order of\n        // the others doesn't matter, and we want to reduce the entropy of the `allowed_company_ids`\n        // key in the context. This is important for the caches, as the stringified context is\n        // always present in the rpc cache keys.\n        activeCompanies = [activeCompanies[0]].concat(\n            sortBy(activeCompanies.slice(1), (c) => c.id)\n        );\n\n        // update browser data\n        cookie.set(\"cids\", activeCompanies.map((c) => c.id).join(\"-\"));\n        Object.assign(context, { allowed_company_ids: activeCompanies.map((c) => c.id) });\n\n        userBus.trigger(\"ACTIVE_COMPANIES_CHANGED\");\n    }\n\n    // Companies information\n    let allowedCompanies = [];\n    const allowedCompaniesWithAncestors = [];\n    let activeCompanies = [];\n    let defaultCompany;\n\n    if (userCompanies) {\n        allowedCompanies = Object.values(userCompanies.allowed_companies);\n        allowedCompaniesWithAncestors.push(...Object.values(userCompanies.allowed_companies));\n        if (userCompanies.disallowed_ancestor_companies) {\n            allowedCompaniesWithAncestors.push(\n                ...Object.values(userCompanies.disallowed_ancestor_companies)\n            );\n        }\n        defaultCompany = allowedCompanies.find((c) => c.id === userCompanies.current_company); // TODO: change the name in the session current_company to default_company\n        updateActiveCompanies(getCookieCompanyIds(), allowedCompanies, defaultCompany);\n    }\n\n    // Delete user-related information from the session, s.t. there's a single source of truth\n    delete session.home_action_id;\n    delete session.is_admin;\n    delete session.is_internal_user;\n    delete session.is_system;\n    delete session.name;\n    delete session.partner_id;\n    delete session.show_effect;\n    delete session.uid;\n    delete session.username;\n    delete session.user_context;\n    delete session.user_settings;\n    delete session.partner_write_date;\n    delete session.user_companies;\n    delete session.groups;\n\n    // Generate caches for has_group and has_access calls\n    const getGroupCacheValue = (group, context) => {\n        if (!userId) {\n            return Promise.resolve(false);\n        }\n        return rpc(\"/web/dataset/call_kw/res.users/has_group\", {\n            model: \"res.users\",\n            method: \"has_group\",\n            args: [userId, group],\n            kwargs: { context },\n        });\n    };\n    const getGroupCacheKey = (group) => group;\n    const groupCache = new Cache(getGroupCacheValue, getGroupCacheKey);\n    if (isInternalUser !== undefined) {\n        groupCache.cache[\"base.group_user\"] = Promise.resolve(isInternalUser);\n    }\n    if (isSystem !== undefined) {\n        groupCache.cache[\"base.group_system\"] = Promise.resolve(isSystem);\n    }\n    if (isAdmin !== undefined) {\n        groupCache.cache[\"base.group_erp_manager\"] = Promise.resolve(isAdmin);\n    }\n    if (isPublic !== undefined) {\n        groupCache.cache[\"base.group_public\"] = Promise.resolve(isPublic);\n    }\n    for (const group in groups) {\n        groupCache.cache[group] = Promise.resolve(!!groups[group]);\n    }\n    const getAccessRightCacheValue = (model, operation, ids, context) => {\n        const url = `/web/dataset/call_kw/${model}/has_access`;\n        return rpc(url, {\n            model,\n            method: \"has_access\",\n            args: [ids, operation],\n            kwargs: { context },\n        });\n    };\n    const getAccessRightCacheKey = (model, operation, ids) =>\n        JSON.stringify([model, operation, ids]);\n    const accessRightCache = new Cache(getAccessRightCacheValue, getAccessRightCacheKey);\n    const lang = pyToJsLocale(context?.lang);\n\n    return {\n        name,\n        login,\n        isAdmin,\n        isSystem,\n        isInternalUser,\n        partnerId,\n        homeActionId,\n        showEffect,\n        userId, // TODO: rename into id?\n        writeDate,\n        get context() {\n            return Object.assign({}, context, { uid: this.userId });\n        },\n        get lang() {\n            return lang;\n        },\n        get tz() {\n            return this.context.tz;\n        },\n        get settings() {\n            return Object.assign({}, settings);\n        },\n        updateContext(update) {\n            Object.assign(context, update);\n        },\n        hasGroup(group) {\n            return groupCache.read(group, this.context);\n        },\n        checkAccessRight(model, operation, ids = []) {\n            return accessRightCache.read(model, operation, ensureArray(ids), this.context);\n        },\n        async setUserSettings(key, value) {\n            const model = \"res.users.settings\";\n            const method = \"set_res_users_settings\";\n            const changedSettings = await rpc(`/web/dataset/call_kw/${model}/${method}`, {\n                model,\n                method,\n                args: [[this.settings.id]],\n                kwargs: {\n                    new_settings: {\n                        [key]: value,\n                    },\n                    context: this.context,\n                },\n            });\n            Object.assign(settings, changedSettings);\n        },\n        updateUserSettings(key, value) {\n            settings[key] = value;\n        },\n        defaultCompany, // default company of the user, used if no cookie set\n        allowedCompanies, // list of authorized companies for the user\n        allowedCompaniesWithAncestors,\n        // list of companies the user is currently logged into\n        get activeCompanies() {\n            return activeCompanies;\n        },\n        // main company the user is currently logged into (default company for created records)\n        get activeCompany() {\n            return activeCompanies?.[0];\n        },\n        async activateCompanies(\n            companyIds,\n            options = { includeChildCompanies: true, reload: true }\n        ) {\n            const newCompanyIds = companyIds.length ? companyIds : [activeCompanies[0].id];\n\n            function addCompanies(companyIds) {\n                for (const companyId of companyIds) {\n                    if (!newCompanyIds.includes(companyId)) {\n                        newCompanyIds.push(companyId);\n                        addCompanies(allowedCompanies.find((c) => c.id === companyId).child_ids);\n                    }\n                }\n            }\n\n            if (options.includeChildCompanies) {\n                addCompanies(\n                    companyIds.flatMap(\n                        (companyId) => allowedCompanies.find((c) => c.id === companyId).child_ids\n                    )\n                );\n            }\n\n            updateActiveCompanies(newCompanyIds, allowedCompanies, defaultCompany);\n\n            if (options.reload) {\n                browser.location.reload();\n            }\n        },\n    };\n}\n\nexport const user = _makeUser(session);\n\nconst LAST_CONNECTED_USER_KEY = \"web.lastConnectedUser\";\n\nexport const getLastConnectedUsers = () => {\n    const lastConnectedUsers = browser.localStorage.getItem(LAST_CONNECTED_USER_KEY);\n    return lastConnectedUsers ? JSON.parse(lastConnectedUsers) : [];\n};\n\nexport const setLastConnectedUsers = (users) => {\n    browser.localStorage.setItem(LAST_CONNECTED_USER_KEY, JSON.stringify(users.slice(0, 5)));\n};\n\nif (!session.quick_login) {\n    browser.localStorage.removeItem(LAST_CONNECTED_USER_KEY);\n} else if (user.login && user.login !== \"__system__\") {\n    const users = getLastConnectedUsers();\n    const lastConnectedUsers = [\n        {\n            login: user.login,\n            name: user.name,\n            partnerId: user.partnerId,\n            partnerWriteDate: user.writeDate,\n            userId: user.userId,\n        },\n        ...users.filter((u) => u.userId !== user.userId),\n    ];\n    setLastConnectedUsers(lastConnectedUsers);\n}\ndelete session.quick_login;\n", "import { Component, useRef, useState, useEffect } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\nimport { getLastConnectedUsers, setLastConnectedUsers } from \"@web/core/user\";\nimport { imageUrl } from \"@web/core/utils/urls\";\n\nexport class UserSwitch extends Component {\n    static template = \"web.login_user_switch\";\n    static props = {};\n\n    setup() {\n        const users = getLastConnectedUsers();\n        this.root = useRef(\"root\");\n        this.state = useState({\n            users,\n            displayUserChoice: users.length > 1,\n        });\n        this.form = document.querySelector(\"form.oe_login_form\");\n        this.form.classList.toggle(\"d-none\", users.length > 1);\n        this.form.querySelector(\":placeholder-shown\")?.focus();\n        useEffect(\n            (el) => el?.querySelector(\"button.list-group-item-action\")?.focus(),\n            () => [this.root.el]\n        );\n    }\n\n    toggleFormDisplay() {\n        this.state.displayUserChoice = !this.state.displayUserChoice && this.state.users.length;\n        this.form.classList.toggle(\"d-none\", this.state.displayUserChoice);\n        this.form.querySelector(\":placeholder-shown\")?.focus();\n    }\n\n    getAvatarUrl({ partnerId, partnerWriteDate: unique }) {\n        return imageUrl(\"res.partner\", partnerId, \"avatar_128\", { unique });\n    }\n\n    remove(deletedUser) {\n        this.state.users = this.state.users.filter((user) => user !== deletedUser);\n        setLastConnectedUsers(this.state.users);\n        if (!this.state.users.length) {\n            this.fillForm();\n        }\n    }\n\n    fillForm(login = \"\") {\n        this.form.querySelector(\"input#login\").value = login;\n        this.form.querySelector(\"input#password\").value = \"\";\n        this.toggleFormDisplay();\n    }\n}\n\nregistry.category(\"public_components\").add(\"web.user_switch\", UserSwitch);\n", "import { shallowEqual as _shallowEqual } from \"./objects\";\n\n/**\n * @template T\n * @template {string | number | symbol} K\n * @typedef {keyof T | ((item: T) => K)} Criterion\n */\n\n/**\n * Same values returned as those returned by cartesian function for case n = 0\n * and n > 1. For n = 1, brackets are put around the unique parameter elements.\n *\n * @template T\n * @param {...T[]} args\n * @returns {T[][]}\n */\nfunction _cartesian(...args) {\n    if (args.length === 0) {\n        return [undefined];\n    }\n    const firstArray = args.shift().map((elem) => [elem]);\n    if (args.length === 0) {\n        return firstArray;\n    }\n    const result = [];\n    const productOfOtherArrays = _cartesian(...args);\n    for (const array of firstArray) {\n        for (const tuple of productOfOtherArrays) {\n            result.push([...array, ...tuple]);\n        }\n    }\n    return result;\n}\n\n/**\n * Helper function returning an extraction handler to use on array elements to\n * return a certain attribute or mutated form of the element.\n *\n * @private\n * @template T\n * @template {string | number | symbol} K\n * @param {Criterion<T, K>} [criterion]\n * @returns {(element: T) => any}\n */\nfunction _getExtractorFrom(criterion) {\n    if (criterion) {\n        switch (typeof criterion) {\n            case \"string\":\n                return (element) => element[criterion];\n            case \"function\":\n                return criterion;\n            default:\n                throw new Error(\n                    `Expected criterion of type 'string' or 'function' and got '${typeof criterion}'`\n                );\n        }\n    } else {\n        return (element) => element;\n    }\n}\n\n/**\n * Returns an array containing either:\n * - the elements contained in the given iterable OR\n * - the given element if it is not an iterable\n *\n * @template T\n * @param {T | Iterable<T>} [value]\n * @returns {T[]}\n */\nexport function ensureArray(value) {\n    return isIterable(value) ? [...value] : [value];\n}\n\n/**\n * Returns the array of elements contained in both arrays.\n *\n * @template T\n * @param {Iterable<T>} iter1\n * @param {Iterable<T>} iter2\n * @returns {T[]}\n */\nexport function intersection(iter1, iter2) {\n    const set2 = new Set(iter2);\n    return unique(iter1).filter((v) => set2.has(v));\n}\n\n/**\n * Returns whether the given value is an iterable object (excluding strings).\n *\n * @param {unknown} value\n */\nexport function isIterable(value) {\n    return Boolean(value && typeof value === \"object\" && value[Symbol.iterator]);\n}\n\n/**\n * Returns an object holding different groups defined by a given criterion\n * or a default one. Each group is a subset of the original given list.\n * The given criterion can either be:\n * - a string: a property name on the list elements which value will be the\n * group name,\n * - a function: a handler that will return the group name from a given\n * element.\n *\n * @template T\n * @template {string | number | symbol} K\n * @param {Iterable<T>} iterable\n * @param {Criterion<T, K>} [criterion]\n * @returns {Record<K, T[]>}\n */\nexport function groupBy(iterable, criterion) {\n    const extract = _getExtractorFrom(criterion);\n    /** @type {Partial<Record<K, T[]>>} */\n    const groups = {};\n    for (const element of iterable) {\n        const group = String(extract(element));\n        if (!(group in groups)) {\n            groups[group] = [];\n        }\n        groups[group].push(element);\n    }\n    return groups;\n}\n\n/**\n * Return a shallow copy of a given array sorted by a given criterion or a default one.\n * The given criterion can either be:\n * - a string: a property name on the array elements returning the sortable primitive\n * - a function: a handler that will return the sortable primitive from a given element.\n * The default order is ascending ('asc'). It can be modified by setting the extra param 'order' to 'desc'.\n *\n * @template T\n * @template {string | number | symbol} K\n * @param {Iterable<T>} iterable\n * @param {Criterion<T, K>} [criterion]\n * @param {\"asc\" | \"desc\"} [order=\"asc\"]\n * @returns {T[]}\n */\nexport function sortBy(iterable, criterion, order = \"asc\") {\n    const extract = _getExtractorFrom(criterion);\n    return [...iterable].sort((elA, elB) => {\n        const a = extract(elA);\n        const b = extract(elB);\n        let result;\n        if (isNaN(a) && isNaN(b)) {\n            result = a > b ? 1 : a < b ? -1 : 0;\n        } else {\n            result = a - b;\n        }\n        return order === \"asc\" ? result : -result;\n    });\n}\n\n/**\n * Returns an array containing all the elements of arrayA\n * that are not in arrayB and vice-versa.\n *\n * @template T\n * @param {Iterable<T>} iter1\n * @param {Iterable<T>} iter2\n * @returns {T[]} an array containing all the elements of iter1\n * that are not in iter2 and vice-versa.\n */\nexport function symmetricalDifference(iter1, iter2) {\n    const array1 = [...iter1];\n    const array2 = [...iter2];\n    return [\n        ...array1.filter((value) => !array2.includes(value)),\n        ...array2.filter((value) => !array1.includes(value)),\n    ];\n}\n\n/**\n * Returns the product of any number n of arrays.\n * The internal structures of their elements is preserved.\n * For n = 1, no brackets are put around the unique parameter elements\n * For n = 0, [undefined] is returned since it is the unit\n * of the cartesian product (up to isomorphism).\n *\n * @template T\n * @param {...T[]} args\n * @returns {T[] | T[][]}\n */\nexport function cartesian(...args) {\n    if (args.length === 0) {\n        return [undefined];\n    } else if (args.length === 1) {\n        return args[0];\n    } else {\n        return _cartesian(...args);\n    }\n}\n\nexport const shallowEqual = _shallowEqual;\n\n/**\n * Returns all initial sections of a given array, e.g. for [1, 2] the array\n * [[], [1], [1, 2]] is returned.\n *\n * @template T\n * @param {Iterable<T>} iterable\n * @returns {T[][]}\n */\nexport function sections(iterable) {\n    const array = [...iterable];\n    const sections = [];\n    for (let i = 0; i < array.length + 1; i++) {\n        sections.push(array.slice(0, i));\n    }\n    return sections;\n}\n\n/**\n * Returns an array containing all elements of the given\n * array but without duplicates.\n *\n * @template T\n * @param {Iterable<T>} iterable\n * @returns {T[]}\n */\nexport function unique(iterable) {\n    return [...new Set(iterable)];\n}\n\n/**\n * @template T1, T2\n * @param {Iterable<T1>} iter1\n * @param {Iterable<T2>} iter2\n * @param {boolean} [fill=false]\n * @returns {[T1, T2][]}\n */\nexport function zip(iter1, iter2, fill = false) {\n    const array1 = [...iter1];\n    const array2 = [...iter2];\n    /** @type {[T1, T2][]} */\n    const result = [];\n    const getLength = fill ? Math.max : Math.min;\n    for (let i = 0; i < getLength(array1.length, array2.length); i++) {\n        result.push([array1[i], array2[i]]);\n    }\n    return result;\n}\n\n/**\n * @template T1, T2, T\n * @param {Iterable<T1>} iter1\n * @param {Iterable<T2>} iter2\n * @param {(e1: T1, e2: T2) => T} mapFn\n * @returns {T[]}\n */\nexport function zipWith(iter1, iter2, mapFn) {\n    return zip(iter1, iter2).map(([e1, e2]) => mapFn(e1, e2));\n}\n/**\n * Creates an sliding window over an array of a given width. Eg:\n * slidingWindow([1, 2, 3, 4], 2) => [[1, 2], [2, 3], [3, 4]]\n *\n * @template T\n * @param {T[]} arr the array over which to create a sliding window\n * @param {number} width the width of the window\n * @returns {T[][]} an array of tuples of size width\n */\nexport function slidingWindow(arr, width) {\n    const res = [];\n    for (let i = 0; i <= arr.length - width; i++) {\n        res.push(arr.slice(i, i + width));\n    }\n    return res;\n}\n\nexport function rotate(i, arr, inc = 1) {\n    return (arr.length + i + inc) % arr.length;\n}\n", "import { useEffect } from \"@odoo/owl\";\nimport { browser } from \"../browser/browser\";\n\n/**\n * This is used on text inputs or textareas to automatically resize it based on its\n * content each time it is updated. It takes the reference of the element as\n * parameter and some options. Do note that it may introduce mild performance issues\n * since it will force a reflow of the layout each time the element is updated.\n * Do also note that it only works with textareas that are nested as only child\n * of some parent div (like in the text_field component).\n *\n * @param {Ref} ref\n */\nexport function useAutoresize(ref, options = {}) {\n    let wasProgrammaticallyResized = false;\n    let resize = null;\n    useEffect(\n        (el) => {\n            if (el) {\n                resize = (programmaticResize = false) => {\n                    wasProgrammaticallyResized = programmaticResize;\n                    if (options.ignoreIfEmpty && !el.value) {\n                        return;\n                    }\n                    if (el instanceof HTMLInputElement) {\n                        resizeInput(el, options);\n                    } else {\n                        resizeTextArea(el, options);\n                    }\n                    options.onResize?.(el, options);\n                };\n                el.addEventListener(\"input\", () => resize(true));\n                const resizeObserver = new ResizeObserver(() => {\n                    // This ensures that the resize function is not called twice on input or page load\n                    if (wasProgrammaticallyResized) {\n                        wasProgrammaticallyResized = false;\n                        return;\n                    }\n                    resize();\n                });\n                resizeObserver.observe(el);\n                return () => {\n                    el.removeEventListener(\"input\", resize);\n                    resizeObserver.unobserve(el);\n                    resizeObserver.disconnect();\n                    resize = null;\n                };\n            }\n        },\n        () => [ref.el]\n    );\n    useEffect(() => {\n        if (resize) {\n            resize(true);\n        }\n    });\n}\n\n/**\n * @param {HTMLInputElement} input\n * @param {{ offset?: number }} [options]\n */\nfunction resizeInput(input, options) {\n    // This mesures the maximum width of the input which can get from the flex layout.\n    input.style.width = \"100%\";\n    const maxWidth = input.clientWidth;\n    // Somehow Safari 16 computes input sizes incorrectly. This is fixed in Safari 17\n    const isSafari16 = /Version\\/16.+Safari/i.test(browser.navigator.userAgent);\n    // Minimum width of the input\n    input.style.width = \"10px\";\n    if (input.value === \"\" && input.placeholder !== \"\") {\n        input.style.width = \"auto\";\n        return;\n    }\n    if (input.scrollWidth + 5 + (isSafari16 ? 8 : 0) > maxWidth) {\n        input.style.width = \"100%\";\n        return;\n    }\n    input.style.width = input.scrollWidth + 5 + (isSafari16 ? 8 : 0) + (options.offset || 0) + \"px\";\n}\n\n/**\n * @param {HTMLTextAreaElement} input\n * @param {{ minimumHeight?: number }} [options]\n */\nexport function resizeTextArea(textarea, options = {}) {\n    const minimumHeight = options.minimumHeight || 0;\n    let heightOffset = 0;\n    const style = window.getComputedStyle(textarea);\n    if (style.boxSizing === \"border-box\") {\n        const paddingHeight = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);\n        const borderHeight = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);\n        heightOffset = borderHeight + paddingHeight;\n    }\n    const previousStyle = {\n        borderTopWidth: style.borderTopWidth,\n        borderBottomWidth: style.borderBottomWidth,\n        padding: style.padding,\n    };\n    Object.assign(textarea.style, {\n        height: \"auto\",\n        borderTopWidth: 0,\n        borderBottomWidth: 0,\n        paddingTop: 0,\n        paddingBottom: 0,\n    });\n    textarea.style.height = \"auto\";\n    const height = Math.max(minimumHeight, textarea.scrollHeight + heightOffset);\n    Object.assign(textarea.style, previousStyle, { height: `${height}px` });\n    textarea.parentElement.style.height = `${height}px`;\n}\n", "import { _t } from \"@web/core/l10n/translation\";\n\n/**\n * @param {string} value\n * @returns {boolean}\n */\nexport function isBinarySize(value) {\n    return /^\\d+(\\.\\d*)? [^0-9]+$/.test(value);\n}\n\n/**\n * Get the length necessary for a base64 str to encode maxBytes\n * @param {number} maxBytes number of bytes we want to encode in base64\n * @returns {number} number of char\n */\nexport function toBase64Length(maxBytes) {\n    return Math.ceil(maxBytes * 4 / 3);\n}\n\n/**\n * @param {number} size number of bytes\n * @param {string}\n */\nexport function humanSize(size) {\n    const units = _t(\"Bytes|Kb|Mb|Gb|Tb|Pb|Eb|Zb|Yb\").split(\"|\");\n    let i = 0;\n    while (size >= 1024) {\n        size /= 1024;\n        ++i;\n    }\n    return `${size.toFixed(2)} ${units[i].trim()}`;\n}\n", "export class Cache {\n    constructor(getValue, getKey) {\n        this.cache = {};\n        this.getKey = getKey;\n        this.getValue = getValue;\n    }\n    _getCacheAndKey(...path) {\n        let cache = this.cache;\n        let key;\n        if (this.getKey) {\n            key = this.getKey(...path);\n        } else {\n            for (let i = 0; i < path.length - 1; i++) {\n                cache = cache[path[i]] = cache[path[i]] || {};\n            }\n            key = path[path.length - 1];\n        }\n        return { cache, key };\n    }\n    clear(...path) {\n        const { cache, key } = this._getCacheAndKey(...path);\n        delete cache[key];\n    }\n    invalidate() {\n        this.cache = {};\n    }\n    read(...path) {\n        const { cache, key } = this._getCacheAndKey(...path);\n        if (!(key in cache)) {\n            cache[key] = this.getValue(...path);\n        }\n        return cache[key];\n    }\n}\n", "/**\n * Adds the given classes to an element, whether the classes\n * are strings or objects.\n *\n * @param {HTMLElement} el\n * @param {String|Object|undefined} classes\n *\n * @example\n * addClassesToElement(el, \"hello\", { \"world\": 0 == 1, }...)\n */\nexport function addClassesToElement(el, ...classes) {\n    for (const classDefinition of classes) {\n        const classObj = toClassObj(classDefinition);\n        for (const className in classObj) {\n            if (classObj[className]) {\n                el.classList.add(className.trim());\n            }\n        }\n    }\n}\n\n/**\n * Merges two classes to a single class object, whether the\n * classes are strings or objects.\n *\n * @param {String|Object|undefined} classes\n * @returns {Object}\n *\n * @example\n * mergeClasses(\"hello\", { \"world\": 0 == 1, }...)\n */\nexport function mergeClasses(...classes) {\n    const classObj = {};\n    for (const classDefinition of classes) {\n        Object.assign(classObj, toClassObj(classDefinition));\n    }\n    return classObj;\n}\n\n/**\n * Returns an object from a class definition, whether it\n * is a string or an object.\n *\n * The returned object keys are css class names and the\n * values are expressions which represent if the class\n * should be added or not.\n *\n * @param {String|Object|undefined} classDefinition\n * @returns {Object}\n */\nfunction toClassObj(classDefinition) {\n    if (!classDefinition) {\n        return {};\n    } else if (typeof classDefinition === \"object\") {\n        return classDefinition;\n    } else if (typeof classDefinition === \"string\") {\n        const classObj = {};\n        classDefinition\n            .trim()\n            .split(/\\s+/)\n            .forEach((s) => {\n                classObj[s] = true;\n            });\n        return classObj;\n    } else {\n        console.warn(\n            `toClassObj only supports strings, objects and undefined className (got ${typeof classProp})`\n        );\n        return {};\n    }\n}\n", "/**\n * Adds opacity to the gradient\n *\n * @static\n * @param {string} gradient - css gradient string\n * @param {number} opacity - [0, 1] {float}\n * @returns {string} - gradient string with opacity\n */\nexport function applyOpacityToGradient(gradient, opacity = 100) {\n    if (opacity === 100) {\n        return gradient;\n    }\n    return gradient.replace(/rgb\\(([^)]+)\\)/g, `rgba($1, ${opacity / 100.0})`);\n}\n/**\n * Converts RGB color components to HSL components.\n *\n * @static\n * @param {integer} r - [0, 255]\n * @param {integer} g - [0, 255]\n * @param {integer} b - [0, 255]\n * @returns {Object|false}\n *          - hue [0, 360[ (float)\n *          - saturation [0, 100] (float)\n *          - lightness [0, 100] (float)\n */\nexport function convertRgbToHsl(r, g, b) {\n    if (\n        typeof r !== \"number\" ||\n        isNaN(r) ||\n        r < 0 ||\n        r > 255 ||\n        typeof g !== \"number\" ||\n        isNaN(g) ||\n        g < 0 ||\n        g > 255 ||\n        typeof b !== \"number\" ||\n        isNaN(b) ||\n        b < 0 ||\n        b > 255\n    ) {\n        return false;\n    }\n\n    var red = r / 255;\n    var green = g / 255;\n    var blue = b / 255;\n    var maxColor = Math.max(red, green, blue);\n    var minColor = Math.min(red, green, blue);\n    var delta = maxColor - minColor;\n    var hue = 0;\n    var saturation = 0;\n    var lightness = (maxColor + minColor) / 2;\n    if (delta) {\n        if (maxColor === red) {\n            hue = (green - blue) / delta;\n        }\n        if (maxColor === green) {\n            hue = 2 + (blue - red) / delta;\n        }\n        if (maxColor === blue) {\n            hue = 4 + (red - green) / delta;\n        }\n        if (maxColor) {\n            saturation = delta / (1 - Math.abs(2 * lightness - 1));\n        }\n    }\n    hue = 60 * hue;\n    return {\n        hue: hue < 0 ? hue + 360 : hue,\n        saturation: saturation * 100,\n        lightness: lightness * 100,\n    };\n}\n/**\n * Converts HSL color components to RGB components.\n *\n * @static\n * @param {number} h - [0, 360[ (float)\n * @param {number} s - [0, 100] (float)\n * @param {number} l - [0, 100] (float)\n * @returns {Object|false}\n *          - red [0, 255] (integer)\n *          - green [0, 255] (integer)\n *          - blue [0, 255] (integer)\n */\nexport function convertHslToRgb(h, s, l) {\n    if (\n        typeof h !== \"number\" ||\n        isNaN(h) ||\n        h < 0 ||\n        h > 360 ||\n        typeof s !== \"number\" ||\n        isNaN(s) ||\n        s < 0 ||\n        s > 100 ||\n        typeof l !== \"number\" ||\n        isNaN(l) ||\n        l < 0 ||\n        l > 100\n    ) {\n        return false;\n    }\n\n    var huePrime = h / 60;\n    var saturation = s / 100;\n    var lightness = l / 100;\n    var chroma = saturation * (1 - Math.abs(2 * lightness - 1));\n    var secondComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));\n    var lightnessAdjustment = lightness - chroma / 2;\n    var precision = 255;\n    chroma = Math.round((chroma + lightnessAdjustment) * precision);\n    secondComponent = Math.round((secondComponent + lightnessAdjustment) * precision);\n    lightnessAdjustment = Math.round(lightnessAdjustment * precision);\n    if (huePrime >= 0 && huePrime < 1) {\n        return {\n            red: chroma,\n            green: secondComponent,\n            blue: lightnessAdjustment,\n        };\n    }\n    if (huePrime >= 1 && huePrime < 2) {\n        return {\n            red: secondComponent,\n            green: chroma,\n            blue: lightnessAdjustment,\n        };\n    }\n    if (huePrime >= 2 && huePrime < 3) {\n        return {\n            red: lightnessAdjustment,\n            green: chroma,\n            blue: secondComponent,\n        };\n    }\n    if (huePrime >= 3 && huePrime < 4) {\n        return {\n            red: lightnessAdjustment,\n            green: secondComponent,\n            blue: chroma,\n        };\n    }\n    if (huePrime >= 4 && huePrime < 5) {\n        return {\n            red: secondComponent,\n            green: lightnessAdjustment,\n            blue: chroma,\n        };\n    }\n    if (huePrime >= 5 && huePrime <= 6) {\n        return {\n            red: chroma,\n            green: lightnessAdjustment,\n            blue: secondComponent,\n        };\n    }\n    return false;\n}\n/**\n * Converts RGBA color components to a normalized CSS color: if the opacity\n * is invalid or equal to 100, a hex color excluding opacity is returned;\n * otherwise a hex color including opacity component is returned.\n *\n * @static\n * @param {integer} r - [0, 255]\n * @param {integer} g - [0, 255]\n * @param {integer} b - [0, 255]\n * @param {float} a - [0, 100]\n * @returns {string}\n */\nexport function convertRgbaToCSSColor(r, g, b, a) {\n    if (\n        typeof r !== \"number\" ||\n        isNaN(r) ||\n        r < 0 ||\n        r > 255 ||\n        typeof g !== \"number\" ||\n        isNaN(g) ||\n        g < 0 ||\n        g > 255 ||\n        typeof b !== \"number\" ||\n        isNaN(b) ||\n        b < 0 ||\n        b > 255\n    ) {\n        return false;\n    }\n    const rr = r < 16 ? \"0\" + r.toString(16) : r.toString(16);\n    const gg = g < 16 ? \"0\" + g.toString(16) : g.toString(16);\n    const bb = b < 16 ? \"0\" + b.toString(16) : b.toString(16);\n    if (\n        typeof a !== \"number\" ||\n        isNaN(a) ||\n        a < 0 ||\n        a > 100 ||\n        Math.abs(a - 100) < Number.EPSILON\n    ) {\n        return `#${rr}${gg}${bb}`.toUpperCase();\n    }\n    const alpha = Math.round((a / 100) * 255);\n    const aa = alpha < 16 ? \"0\" + alpha.toString(16) : alpha.toString(16);\n    return `#${rr}${gg}${bb}${aa}`.toUpperCase();\n}\n/**\n * Converts a CSS color (rgb(), rgba(), hexadecimal) to RGBA color components.\n *\n * Note: we don't support using and displaying hexadecimal color with opacity\n * but this method allows to receive one and returns the correct opacity value.\n *\n * @static\n * @param {string} cssColor - hexadecimal code or rgb() or rgba() or color()\n * @returns {Object|false}\n *          - red [0, 255] (integer)\n *          - green [0, 255] (integer)\n *          - blue [0, 255] (integer)\n *          - opacity [0, 100.0] (float)\n */\nexport function convertCSSColorToRgba(cssColor = \"\") {\n    // Check if cssColor is a rgba() or rgb() color\n    const rgba = cssColor.match(/^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d*(?:\\.\\d+)?))?\\)$/);\n    if (rgba) {\n        if (rgba[4] === undefined) {\n            rgba[4] = 1;\n        }\n        return {\n            red: parseInt(rgba[1]),\n            green: parseInt(rgba[2]),\n            blue: parseInt(rgba[3]),\n            opacity: Math.round(parseFloat(rgba[4]) * 100),\n        };\n    }\n\n    // Otherwise, check if cssColor is an hexadecimal code color\n    // first check if it's in its compact form (e.g. #FFF)\n    if (/^#([0-9a-f]{3})$/i.test(cssColor)) {\n        return {\n            red: parseInt(cssColor[1] + cssColor[1], 16),\n            green: parseInt(cssColor[2] + cssColor[2], 16),\n            blue: parseInt(cssColor[3] + cssColor[3], 16),\n            opacity: 100,\n        };\n    }\n\n    if (/^#([0-9A-F]{6}|[0-9A-F]{8})$/i.test(cssColor)) {\n        return {\n            red: parseInt(cssColor.substr(1, 2), 16),\n            green: parseInt(cssColor.substr(3, 2), 16),\n            blue: parseInt(cssColor.substr(5, 2), 16),\n            opacity: (cssColor.length === 9 ? parseInt(cssColor.substr(7, 2), 16) / 255 : 1) * 100,\n        };\n    }\n\n    // TODO maybe implement a support for receiving css color like 'red' or\n    // 'transparent' (which are now considered non-css color by isCSSColor...)\n    // Note: however, if ever implemented be careful of 'white'/'black' which\n    // actually are color names for our color system...\n\n    // Check if cssColor is a color() functional notation allowing colorspace\n    // with implicit sRGB.\n    // \"<color()>\" allows to define a color specification in a formalized\n    // manner. It starts with the \"color(\" keyword, specifies color space\n    // parameters, and optionally includes an alpha value for transparency.\n    if (/color\\(.+\\)/.test(cssColor)) {\n        const canvasEl = document.createElement(\"canvas\");\n        canvasEl.height = 1;\n        canvasEl.width = 1;\n        const ctx = canvasEl.getContext(\"2d\");\n        ctx.fillStyle = cssColor;\n        ctx.fillRect(0, 0, 1, 1);\n        const data = ctx.getImageData(0, 0, 1, 1).data;\n        return {\n            red: data[0],\n            green: data[1],\n            blue: data[2],\n            opacity: data[3] / 2.55, // Convert 0-255 to percentage\n        };\n    }\n    return false;\n}\n/**\n * Converts a CSS color (rgb(), rgba(), hexadecimal) to a normalized version\n * of the same color (@see convertRgbaToCSSColor).\n *\n * Normalized color can be safely compared using string comparison.\n *\n * @static\n * @param {string} cssColor - hexadecimal code or rgb() or rgba()\n * @returns {string} - the normalized css color or the given css color if it\n *                     failed to be normalized\n */\nexport function normalizeCSSColor(cssColor) {\n    const rgba = convertCSSColorToRgba(cssColor);\n    if (!rgba) {\n        return cssColor;\n    }\n    return convertRgbaToCSSColor(rgba.red, rgba.green, rgba.blue, rgba.opacity);\n}\n/**\n * Checks if a given string is a css color.\n *\n * @static\n * @param {string} cssColor\n * @returns {boolean}\n */\nexport function isCSSColor(cssColor) {\n    return convertCSSColorToRgba(cssColor) !== false;\n}\n/**\n * Mixes two colors by applying a weighted average of their red, green and blue\n * components.\n *\n * @static\n * @param {string} cssColor1 - hexadecimal code or rgb() or rgba()\n * @param {string} cssColor2 - hexadecimal code or rgb() or rgba()\n * @param {number} weight - a number between 0 and 1\n * @returns {string} - mixed color in hexadecimal format\n */\nexport function mixCssColors(cssColor1, cssColor2, weight) {\n    const rgba1 = convertCSSColorToRgba(cssColor1);\n    const rgba2 = convertCSSColorToRgba(cssColor2);\n    const rgb1 = [rgba1.red, rgba1.green, rgba1.blue];\n    const rgb2 = [rgba2.red, rgba2.green, rgba2.blue];\n    const [r, g, b] = rgb1.map((_, idx) =>\n        Math.round(rgb2[idx] + (rgb1[idx] - rgb2[idx]) * weight)\n    );\n    return convertRgbaToCSSColor(r, g, b);\n}\n\n/**\n * @param {string} [value]\n * @returns {boolean}\n */\nexport function isColorGradient(value) {\n    return value && value.includes(\"-gradient(\");\n}\n\n/**\n * @param {string} gradient\n * @returns {string} standardized gradient\n */\nexport function standardizeGradient(gradient) {\n    if (isColorGradient(gradient)) {\n        const el = document.createElement(\"div\");\n        el.style.setProperty(\"background-image\", gradient);\n        gradient = el.style.getPropertyValue(\"background-image\");\n    }\n    return gradient;\n}\n\nexport const RGBA_REGEX = /[\\d.]{1,5}/g;\n\n/**\n * Takes a color (rgb, rgba or hex) and returns its hex representation. If the\n * color is given in rgba, the background color of the node whose color we're\n * converting is used in conjunction with the alpha to compute the resulting\n * color (using the formula: `alpha*color + (1 - alpha)*background` for each\n * channel).\n *\n * @param {string} rgb\n * @param {HTMLElement} [node]\n * @returns {string} hexadecimal color (#RRGGBB)\n */\nexport function rgbToHex(rgb = \"\", node = null) {\n    if (rgb.startsWith(\"#\")) {\n        return rgb;\n    } else if (rgb.startsWith(\"rgba\")) {\n        const values = rgb.match(RGBA_REGEX) || [];\n        const alpha = parseFloat(values.pop());\n        // Retrieve the background color.\n        let bgRgbValues = [];\n        if (node) {\n            let bgColor = getComputedStyle(node).backgroundColor;\n            if (bgColor.startsWith(\"rgba\")) {\n                // The background color is itself rgba so we need to compute\n                // the resulting color using the background color of its\n                // parent.\n                bgColor = rgbToHex(bgColor, node.parentElement);\n            }\n            if (bgColor && bgColor.startsWith(\"#\")) {\n                bgRgbValues = (bgColor.match(/[\\da-f]{2}/gi) || []).map((val) => parseInt(val, 16));\n            } else if (bgColor && bgColor.startsWith(\"rgb\")) {\n                bgRgbValues = (bgColor.match(RGBA_REGEX) || []).map((val) => parseInt(val));\n            }\n        }\n        bgRgbValues = bgRgbValues.length ? bgRgbValues : [255, 255, 255]; // Default to white.\n\n        return (\n            \"#\" +\n            values\n                .map((value, index) => {\n                    const converted = Math.floor(\n                        alpha * parseInt(value) + (1 - alpha) * bgRgbValues[index]\n                    );\n                    const hex = parseInt(converted).toString(16);\n                    return hex.length === 1 ? \"0\" + hex : hex;\n                })\n                .join(\"\")\n        );\n    } else {\n        return (\n            \"#\" +\n            (rgb.match(/\\d{1,3}/g) || [])\n                .map((x) => {\n                    x = parseInt(x).toString(16);\n                    return x.length === 1 ? \"0\" + x : x;\n                })\n                .join(\"\")\n        );\n    }\n}\n\n/**\n * Converts an RGBA or RGB color string to a hexadecimal color string.\n * - If the input color is already in hex format, it returns the hex string directly.\n * - If the input color is in rgba format, it converts it to a hex string, including the alpha value.\n * - If the input color is in rgb format, it converts it to a hex string (with no alpha).\n *\n * @param {string} rgba - The color string to convert (can be in RGBA, RGB, or hex format).\n * @returns {string} - The resulting color in hex format (including alpha if applicable).\n */\nexport function rgbaToHex(rgba = \"\") {\n    if (rgba.startsWith(\"#\")) {\n        return rgba;\n    } else if (rgba.startsWith(\"rgba\")) {\n        const values = rgba.match(RGBA_REGEX) || [];\n        return convertRgbaToCSSColor(\n            parseInt(values[0]),\n            parseInt(values[1]),\n            parseInt(values[2]),\n            parseFloat(values[3]) * 100\n        );\n    } else {\n        return rgbToHex(rgba);\n    }\n}\n\n/**\n * Blends an RGBA color with the background color of a given DOM node.\n * - If the input color is not RGBA, it is converted to hex.\n * - If the node has an RGBA background, the function recursively blends it with its parent's background.\n * - If no valid background is found, it defaults to white (#FFFFFF).\n *\n * @param {string} color - The RGBA color to blend.\n * @param {HTMLElement|null} node - The DOM node to get the background color from.\n * @returns {string} - The resulting blended color as a hex string.\n */\nexport function blendColors(color, node) {\n    if (!color.startsWith(\"rgba\")) {\n        return rgbaToHex(color);\n    }\n    let bgRgbValues = [255, 255, 255];\n    if (node) {\n        let bgColor = getComputedStyle(node).backgroundColor;\n\n        if (bgColor.startsWith(\"rgba\")) {\n            // The background color is itself rgba so we need to compute\n            // the resulting color using the background color of its\n            // parent.\n            bgColor = blendColors(bgColor, node.parentElement);\n        }\n        if (bgColor.startsWith(\"#\")) {\n            bgRgbValues = (bgColor.match(/[\\da-f]{2}/gi) || []).map((val) => parseInt(val, 16));\n        } else if (bgColor.startsWith(\"rgb\")) {\n            bgRgbValues = (bgColor.match(/[\\d.]{1,5}/g) || []).map((val) => parseInt(val));\n        }\n    }\n\n    const values = color.match(/[\\d.]{1,5}/g) || [];\n    const alpha = values.length === 4 ? parseFloat(values.pop()) : 1;\n\n    return (\n        \"#\" +\n        values\n            .map((value, index) => {\n                const converted = Math.round(\n                    alpha * parseInt(value) + (1 - alpha) * bgRgbValues[index]\n                );\n                const hex = parseInt(converted).toString(16);\n                return hex.length === 1 ? \"0\" + hex : hex;\n            })\n            .join(\"\")\n    );\n}\n", "import { Component, onError, xml } from \"@odoo/owl\";\n\nexport class ErrorHandler extends Component {\n    static template = xml`<t t-slot=\"default\" />`;\n    static props = [\"onError\", \"slots\"];\n    setup() {\n        onError((error) => {\n            this.props.onError(error);\n        });\n    }\n}\n", "/**\n * Returns a promise resolved after 'wait' milliseconds\n *\n * @param {int} [wait=0] the delay in ms\n * @return {Promise}\n */\nexport function delay(wait) {\n    return new Promise(function (resolve) {\n        setTimeout(resolve, wait);\n    });\n}\n\n/**\n * KeepLast is a concurrency primitive that manages a list of tasks, and only\n * keeps the last task active.\n *\n * @template T\n */\nexport class KeepLast {\n    constructor() {\n        this._id = 0;\n    }\n    /**\n     * Register a new task\n     *\n     * @param {Promise<T>} promise\n     * @returns {Promise<T>}\n     */\n    add(promise) {\n        this._id++;\n        const currentId = this._id;\n        return new Promise((resolve, reject) => {\n            promise\n                .then((value) => {\n                    if (this._id === currentId) {\n                        resolve(value);\n                    }\n                })\n                .catch((reason) => {\n                    // not sure about this part\n                    if (this._id === currentId) {\n                        reject(reason);\n                    }\n                });\n        });\n    }\n}\n\n/**\n * A (Odoo) mutex is a primitive for serializing computations.  This is\n * useful to avoid a situation where two computations modify some shared\n * state and cause some corrupted state.\n *\n * Imagine that we have a function to fetch some data _load(), which returns\n * a promise which resolves to something useful. Now, we have some code\n * looking like this::\n *\n *      return this._load().then(function (result) {\n *          this.state = result;\n *      });\n *\n * If this code is run twice, but the second execution ends before the\n * first, then the final state will be the result of the first call to\n * _load.  However, if we have a mutex::\n *\n *      this.mutex = new Mutex();\n *\n * and if we wrap the calls to _load in a mutex::\n *\n *      return this.mutex.exec(function() {\n *          return this._load().then(function (result) {\n *              this.state = result;\n *          });\n *      });\n *\n * Then, it is guaranteed that the final state will be the result of the\n * second execution.\n *\n * A Mutex has to be a class, and not a function, because we have to keep\n * track of some internal state.\n */\nexport class Mutex {\n    constructor() {\n        this._lock = Promise.resolve();\n        this._queueSize = 0;\n        this._unlockedProm = undefined;\n        this._unlock = undefined;\n    }\n    /**\n     * Add a computation to the queue, it will be executed as soon as the\n     * previous computations are completed.\n     *\n     * @param {() => (void | Promise<void>)} action a function which may return a Promise\n     * @returns {Promise<void>}\n     */\n    async exec(action) {\n        this._queueSize++;\n        if (!this._unlockedProm) {\n            this._unlockedProm = new Promise((resolve) => {\n                this._unlock = () => {\n                    resolve();\n                    this._unlockedProm = undefined;\n                };\n            });\n        }\n        const always = () => {\n            return Promise.resolve(action()).finally(() => {\n                if (--this._queueSize === 0) {\n                    this._unlock();\n                }\n            });\n        };\n        this._lock = this._lock.then(always, always);\n        return this._lock;\n    }\n    /**\n     * @returns {Promise<void>} resolved as soon as the Mutex is unlocked\n     *   (directly if it is currently idle)\n     */\n    getUnlockedDef() {\n        return this._unlockedProm || Promise.resolve();\n    }\n}\n\n/**\n * Race is a class designed to manage concurrency problems inspired by\n * Promise.race(), except that it is dynamic in the sense that promises can be\n * added anytime to a Race instance. When a promise is added, it returns another\n * promise which resolves as soon as a promise, among all added promises, is\n * resolved. The race is thus over. From that point, a new race will begin the\n * next time a promise will be added.\n *\n * @template T\n */\nexport class Race {\n    constructor() {\n        this.currentProm = null;\n        this.currentPromResolver = null;\n        this.currentPromRejecter = null;\n    }\n    /**\n     * Register a new promise. If there is an ongoing race, the promise is added\n     * to that race. Otherwise, it starts a new race. The returned promise\n     * resolves as soon as the race is over, with the value of the first resolved\n     * promise added to the race.\n     *\n     * @param {Promise<T>} promise\n     * @returns {Promise<T>}\n     */\n    add(promise) {\n        if (!this.currentProm) {\n            this.currentProm = new Promise((resolve, reject) => {\n                this.currentPromResolver = (value) => {\n                    this.currentProm = null;\n                    this.currentPromResolver = null;\n                    this.currentPromRejecter = null;\n                    resolve(value);\n                };\n                this.currentPromRejecter = (error) => {\n                    this.currentProm = null;\n                    this.currentPromResolver = null;\n                    this.currentPromRejecter = null;\n                    reject(error);\n                };\n            });\n        }\n        promise.then(this.currentPromResolver).catch(this.currentPromRejecter);\n        return this.currentProm;\n    }\n    /**\n     * @returns {Promise<T>|null} promise resolved as soon as the race is over, or\n     *   null if there is no race ongoing)\n     */\n    getCurrentProm() {\n        return this.currentProm;\n    }\n}\n\n/**\n * Deferred is basically a resolvable/rejectable extension of Promise.\n */\nexport class Deferred extends Promise {\n    constructor() {\n        let resolve;\n        let reject;\n        const prom = new Promise((res, rej) => {\n            resolve = res;\n            reject = rej;\n        });\n        return Object.assign(prom, { resolve, reject });\n    }\n}\n", "import { makeDraggableHook } from \"@web/core/utils/draggable_hook_builder_owl\";\nimport { pick } from \"@web/core/utils/objects\";\n\n/** @typedef {import(\"@web/core/utils/draggable_hook_builder\").DraggableHandlerParams} DraggableHandlerParams */\n\n/**\n * @typedef DraggableParams\n *\n * MANDATORY\n *\n * @property {{ el: HTMLElement | null }} ref\n * @property {string} elements defines draggable elements\n *\n * OPTIONAL\n *\n * @property {boolean | () => boolean} [enable] whether the draggable system should\n *  be enabled.\n * @property {string | () => string} [handle] additional selector for when the dragging\n *  sequence must be initiated when dragging on a certain part of the element.\n * @property {string | () => string} [ignore] selector targetting elements that must\n *  initiate a drag.\n * @property {string | () => string} [cursor] cursor style during the dragging sequence.\n *\n * HANDLERS (also optional)\n *\n * @property {(params: DraggableHandlerParams) => any} [onDragStart]\n *  called when a dragging sequence is initiated.\n * @property {(params: DraggableHandlerParams) => any} [onDrag]\n *  called on each \"mousemove\" during the drag sequence.\n * @property {(params: DraggableHandlerParams) => any} [onDragEnd]\n *  called when the dragging sequence ends, regardless of the reason.\n * @property {(params: DraggableHandlerParams) => any} [onDrop] called when the dragging sequence\n *  ends on a mouseup action.\n */\n\n/**\n * @typedef DraggableState\n * @property {boolean} dragging\n */\n\n/** @type {(params: DraggableParams) => DraggableState} */\nexport const useDraggable = makeDraggableHook({\n    name: \"useDraggable\",\n    onWillStartDrag: ({ ctx }) => pick(ctx.current, \"element\"),\n    onDragStart: ({ ctx }) => pick(ctx.current, \"element\"),\n    onDrag: ({ ctx }) => pick(ctx.current, \"element\"),\n    onDragEnd: ({ ctx }) => pick(ctx.current, \"element\"),\n    onDrop: ({ ctx }) => pick(ctx.current, \"element\"),\n});\n", "import { clamp } from \"@web/core/utils/numbers\";\nimport { omit } from \"@web/core/utils/objects\";\nimport { closestScrollableX, closestScrollableY } from \"@web/core/utils/scrolling\";\nimport { setRecurringAnimationFrame } from \"@web/core/utils/timing\";\nimport { browser } from \"../browser/browser\";\nimport { hasTouch, isBrowserFirefox, isIOS } from \"../browser/feature_detection\";\n\n/**\n * @typedef {ReturnType<typeof makeCleanupManager>} CleanupManager\n *\n * @typedef {ReturnType<typeof makeDOMHelpers>} DOMHelpers\n *\n * @typedef DraggableBuilderParams\n * Hook params\n * @property {string} [name=\"useAnonymousDraggable\"]\n * @property {EdgeScrollingOptions} [edgeScrolling]\n * @property {Record<string, string[]>} [acceptedParams]\n * @property {Record<string, any>} [defaultParams]\n * Setup hooks\n * @property {{\n *  addListener: typeof import(\"@odoo/owl\")[\"useExternalListener\"];\n *  setup: typeof import(\"@odoo/owl\")[\"useEffect\"];\n *  teardown: typeof import(\"@odoo/owl\")[\"onWillUnmount\"];\n *  throttle: typeof import(\"./timing\")[\"useThrottleForAnimation\"];\n *  wrapState: typeof import(\"@odoo/owl\")[\"reactive\"];\n * }} setupHooks\n * Build hooks\n * @property {(params: DraggableBuildHandlerParams) => any} onComputeParams\n * Runtime hooks\n * @property {(params: DraggableBuildHandlerParams) => any} onDragStart\n * @property {(params: DraggableBuildHandlerParams) => any} onDrag\n * @property {(params: DraggableBuildHandlerParams) => any} onDragEnd\n * @property {(params: DraggableBuildHandlerParams) => any} onDrop\n * @property {(params: DraggableBuildHandlerParams) => any} onWillStartDrag\n *\n * @typedef DraggableHookContext\n * @property {{ el: HTMLElement | null }} ref\n * @property {string | null} [elementSelector=null]\n * @property {string | null} [ignoreSelector=null]\n * @property {string | null} [fullSelector=null]\n * @property {boolean} [followCursor=true]\n * @property {string | null} [cursor=null]\n * @property {() => boolean} [enable=() => false]\n * @property {(HTMLElement) => boolean} [preventDrag=(el) => false]\n * @property {Position} [pointer={ x: 0, y: 0 }]\n * @property {EdgeScrollingOptions} [edgeScrolling]\n * @property {number} [delay]\n * @property {number} [tolerance]\n * @property {DraggableHookCurrentContext} current\n *\n * @typedef DraggableHookCurrentContext\n * @property {HTMLElement} [current.container]\n * @property {DOMRect} [current.containerRect]\n * @property {HTMLElement} [current.element]\n * @property {DOMRect} [current.elementRect]\n * @property {HTMLElement | null} [current.scrollParentX]\n * @property {DOMRect | null} [current.scrollParentXRect]\n * @property {HTMLElement | null} [current.scrollParentY]\n * @property {DOMRect | null} [current.scrollParentYRect]\n * @property {\"left\"|\"right\"|\"top\"|\"bottom\"|null} [scrollingEdge]\n * @property {number} [timeout]\n * @property {Position} [initialPosition]\n * @property {Position} [offset={ x: 0, y: 0 }]\n *\n * @typedef EdgeScrollingOptions\n * @property {boolean} [enabled=true]\n * @property {number} [speed=10]\n * @property {number} [threshold=20]\n * @property {\"horizontal\"|\"vertical\"} [direction]\n *\n * @typedef Position\n * @property {number} x\n * @property {number} y\n *\n * @typedef {DOMHelpers & {\n *  ctx: DraggableHookContext,\n *  addCleanup(cleanupFn: () => any): void,\n *  addEffectCleanup(cleanupFn: () => any): void,\n *  callHandler(handlerName: string, arg: Record<any, any>): void,\n * }} DraggableBuildHandlerParams\n *\n * @typedef {DOMHelpers & Position & { element: HTMLElement }} DraggableHandlerParams\n */\n\nconst DRAGGABLE_CLASS = \"o_draggable\";\nexport const DRAGGED_CLASS = \"o_dragged\";\n\nconst DEFAULT_ACCEPTED_PARAMS = {\n    allowDisconnected: [Boolean], // do not use, introduced for stable versions, to challenge in master\n    enable: [Boolean, Function],\n    preventDrag: [Function],\n    ref: [Object],\n    elements: [String],\n    handle: [String, Function],\n    ignore: [String, Function],\n    cursor: [String],\n    edgeScrolling: [Object, Function],\n    delay: [Number],\n    tolerance: [Number],\n    touchDelay: [Number],\n    iframeWindow: [Object, Function],\n};\nconst DEFAULT_DEFAULT_PARAMS = {\n    allowDisconnected: false,\n    elements: `.${DRAGGABLE_CLASS}`,\n    enable: true,\n    preventDrag: () => false,\n    edgeScrolling: {\n        speed: 10,\n        threshold: 30,\n    },\n    delay: 0,\n    tolerance: 10,\n    touchDelay: 300,\n};\nconst LEFT_CLICK = 0;\nconst MANDATORY_PARAMS = [\"ref\"];\nconst WHITE_LISTED_KEYS = [\"Alt\", \"Control\", \"Meta\", \"Shift\"];\n\n/**\n * Cache containing the elements in which an attribute has been modified by a hook.\n * It is global since multiple draggable hooks can interact with the same elements.\n * @type {Record<string, Set<HTMLElement>>}\n */\nconst elCache = {};\n\n/**\n * Transforms a camelCased string to return its kebab-cased version.\n * Typically used to generate CSS properties from JS objects.\n *\n * @param {string} str\n * @returns {string}\n */\nfunction camelToKebab(str) {\n    return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n}\n\n/**\n * @template T\n * @param {T | () => T} valueOrFn\n * @returns {T}\n */\nfunction getReturnValue(valueOrFn) {\n    if (typeof valueOrFn === \"function\") {\n        return valueOrFn();\n    }\n    return valueOrFn;\n}\n\n/**\n * Returns the first scrollable parent of the given element (recursively), or null\n * if none is found. A 'scrollable' element is defined by 2 things:\n *\n * - for either in width or in height: the 'scroll' value is larger than the 'client'\n * value;\n *\n * - its computed 'overflow' property is set to either \"auto\" or \"scroll\"\n *\n * If both of these assertions are true, it means that the element can effectively\n * be scrolled on at least one axis.\n * @param {HTMLElement} el\n * @returns {(HTMLElement | null)[]}\n */\nfunction getScrollParents(el) {\n    return [closestScrollableX(el), closestScrollableY(el)];\n}\n\n/**\n * @param {() => any} [defaultCleanupFn]\n */\nfunction makeCleanupManager(defaultCleanupFn) {\n    /**\n     * Registers the given cleanup function to be called when cleaning up hooks.\n     * @param {() => any} [cleanupFn]\n     */\n    const add = (cleanupFn) => typeof cleanupFn === \"function\" && cleanups.push(cleanupFn);\n\n    /**\n     * Runs all cleanup functions while clearing the cleanups list.\n     */\n    const cleanup = () => {\n        while (cleanups.length) {\n            cleanups.pop()();\n        }\n        add(defaultCleanupFn);\n    };\n\n    const cleanups = [];\n\n    add(defaultCleanupFn);\n\n    return { add, cleanup };\n}\n\n/**\n * @param {CleanupManager} cleanup\n */\nfunction makeDOMHelpers(cleanup) {\n    /**\n     * @param {HTMLElement} el\n     * @param  {...string} classNames\n     */\n    const addClass = (el, ...classNames) => {\n        if (!el || !classNames.length) {\n            return;\n        }\n        cleanup.add(() => el.classList.remove(...classNames));\n        el.classList.add(...classNames);\n    };\n\n    /**\n     * Adds an event listener to be cleaned up after the next drag sequence\n     * has stopped.\n     * @param {EventTarget} el\n     * @param {string} event\n     * @param {(...args: any[]) => any} callback\n     * @param {AddEventListenerOptions & { noAddedStyle?: boolean }} [options]\n     */\n    const addListener = (el, event, callback, options = {}) => {\n        if (!el || !event || !callback) {\n            return;\n        }\n        const { noAddedStyle } = options;\n        delete options.noAddedStyle;\n        el.addEventListener(event, callback, options);\n        if (!noAddedStyle && /mouse|pointer|touch/.test(event)) {\n            // Restore pointer events on elements listening on mouse/pointer/touch events.\n            addStyle(el, { pointerEvents: \"auto\" });\n        }\n        cleanup.add(() => el.removeEventListener(event, callback, options));\n    };\n\n    /**\n     * Adds style to an element to be cleaned up after the next drag sequence has\n     * stopped.\n     * @param {HTMLElement} el\n     * @param {Record<string, string | number>} style\n     */\n    const addStyle = (el, style) => {\n        if (!el || !style || !Object.keys(style).length) {\n            return;\n        }\n        cleanup.add(saveAttribute(el, \"style\"));\n        for (const key in style) {\n            const [value, priority] = String(style[key]).split(/\\s*!\\s*/);\n            el.style.setProperty(camelToKebab(key), value, priority);\n        }\n    };\n\n    /**\n     * Returns the bounding rect of the given element. If the `adjust` option is set\n     * to true, the rect will be reduced by the padding of the element.\n     * @param {HTMLElement} el\n     * @param {Object} [options={}]\n     * @param {boolean} [options.adjust=false]\n     * @returns {DOMRect}\n     */\n    const getRect = (el, options = {}) => {\n        if (!el) {\n            return {};\n        }\n        const rect = el.getBoundingClientRect();\n        if (options.adjust) {\n            const style = getComputedStyle(el);\n            const [pl, pr, pt, pb] = [\n                \"padding-left\",\n                \"padding-right\",\n                \"padding-top\",\n                \"padding-bottom\",\n            ].map((prop) => pixelValueToNumber(style.getPropertyValue(prop)));\n\n            rect.x += pl;\n            rect.y += pt;\n            rect.width -= pl + pr;\n            rect.height -= pt + pb;\n        }\n        return rect;\n    };\n\n    /**\n     * @param {HTMLElement} el\n     * @param {string} attribute\n     */\n    const removeAttribute = (el, attribute) => {\n        if (!el || !attribute) {\n            return;\n        }\n        cleanup.add(saveAttribute(el, attribute));\n        el.removeAttribute(attribute);\n    };\n\n    /**\n     * @param {HTMLElement} el\n     * @param {...string} classNames\n     */\n    const removeClass = (el, ...classNames) => {\n        if (!el || !classNames.length) {\n            return;\n        }\n        cleanup.add(saveAttribute(el, \"class\"));\n        el.classList.remove(...classNames);\n    };\n\n    /**\n     * Adds style to an element to be cleaned up after the next drag sequence has\n     * stopped.\n     * @param {HTMLElement} el\n     * @param {...string} properties\n     */\n    const removeStyle = (el, ...properties) => {\n        if (!el || !properties.length) {\n            return;\n        }\n        cleanup.add(saveAttribute(el, \"style\"));\n        for (const key of properties) {\n            el.style.removeProperty(camelToKebab(key));\n        }\n    };\n\n    /**\n     * @param {HTMLElement} el\n     * @param {string} attribute\n     * @param {any} value\n     */\n    const setAttribute = (el, attribute, value) => {\n        if (!el || !attribute) {\n            return;\n        }\n        cleanup.add(saveAttribute(el, attribute));\n        el.setAttribute(attribute, String(value));\n    };\n\n    return {\n        addClass,\n        addListener,\n        addStyle,\n        getRect,\n        removeAttribute,\n        removeClass,\n        removeStyle,\n        setAttribute,\n    };\n}\n\n/**\n * Converts a CSS pixel value to a number, removing the 'px' part.\n * @param {string} val\n * @returns {number}\n */\nfunction pixelValueToNumber(val) {\n    return Number(val.endsWith(\"px\") ? val.slice(0, -2) : val);\n}\n\n/**\n * @param {Event} ev\n * @param {{ stop?: boolean }} params\n */\nfunction safePrevent(ev, { stop } = {}) {\n    if (ev.cancelable) {\n        ev.preventDefault();\n        if (stop) {\n            ev.stopPropagation();\n        }\n    }\n}\n\nfunction saveAttribute(el, attribute) {\n    const restoreAttribute = () => {\n        cache.delete(el);\n        if (hasAttribute) {\n            el.setAttribute(attribute, originalValue);\n        } else {\n            el.removeAttribute(attribute);\n        }\n    };\n\n    if (!(attribute in elCache)) {\n        elCache[attribute] = new Set();\n    }\n    const cache = elCache[attribute];\n\n    if (cache.has(el)) {\n        return;\n    }\n\n    cache.add(el);\n    const hasAttribute = el.hasAttribute(attribute);\n    const originalValue = el.getAttribute(attribute);\n\n    return restoreAttribute;\n}\n\n/**\n * @template T\n * @param {T | () => T} value\n * @returns {() => T}\n */\nfunction toFunction(value) {\n    return typeof value === \"function\" ? value : () => value;\n}\n\n/**\n * @param {DraggableBuilderParams} hookParams\n * @returns {(params: Record<keyof typeof DEFAULT_ACCEPTED_PARAMS, any>) => { dragging: boolean }}\n */\nexport function makeDraggableHook(hookParams) {\n    hookParams = getReturnValue(hookParams);\n\n    const hookName = hookParams.name || \"useAnonymousDraggable\";\n    const { setupHooks } = hookParams;\n    const allAcceptedParams = { ...DEFAULT_ACCEPTED_PARAMS, ...hookParams.acceptedParams };\n    const defaultParams = { ...DEFAULT_DEFAULT_PARAMS, ...hookParams.defaultParams };\n\n    /**\n     * Computes the current params and converts the params definition\n     * @param {SortableParams} params\n     * @returns {[string, string | boolean][]}\n     */\n    const computeParams = (params) => {\n        const computedParams = { enable: () => true };\n        for (const prop in allAcceptedParams) {\n            if (prop in params) {\n                if (prop === \"enable\") {\n                    computedParams[prop] = toFunction(params[prop]);\n                } else if (\n                    allAcceptedParams[prop].length === 1 &&\n                    allAcceptedParams[prop][0] === Function\n                ) {\n                    computedParams[prop] = params[prop];\n                } else {\n                    computedParams[prop] = getReturnValue(params[prop]);\n                }\n            }\n        }\n        return Object.entries(computedParams);\n    };\n\n    /**\n     * Basic error builder for the hook.\n     * @param {string} reason\n     * @returns {Error}\n     */\n    const makeError = (reason) => new Error(`Error in hook ${hookName}: ${reason}.`);\n    let preventClick = false;\n\n    return {\n        [hookName](params) {\n            /**\n             * Executes a handler from the `hookParams`.\n             * @param {string} hookHandlerName\n             * @param {Record<any, any>} arg\n             */\n            const callBuildHandler = (hookHandlerName, arg) => {\n                if (typeof hookParams[hookHandlerName] !== \"function\") {\n                    return;\n                }\n                const returnValue = hookParams[hookHandlerName]({ ctx, ...helpers, ...arg });\n                if (returnValue) {\n                    callHandler(hookHandlerName, returnValue);\n                }\n            };\n\n            /**\n             * Safely executes a handler from the `params`, so that the drag sequence can\n             * be interrupted if an error occurs.\n             * @param {string} handlerName\n             * @param {Record<any, any>} arg\n             */\n            const callHandler = (handlerName, arg) => {\n                if (typeof params[handlerName] !== \"function\") {\n                    return;\n                }\n                try {\n                    params[handlerName]({ ...dom, ...ctx.pointer, ...arg });\n                } catch (err) {\n                    dragEnd(null, true);\n                    throw err;\n                }\n            };\n\n            /**\n             * Returns whether the user has moved from at least the number of pixels\n             * that are tolerated from the initial pointer position.\n             */\n            const canStartDrag = () => {\n                const {\n                    pointer,\n                    current: { initialPosition },\n                } = ctx;\n                return (\n                    !ctx.tolerance ||\n                    Math.hypot(pointer.x - initialPosition.x, pointer.y - initialPosition.y) >=\n                        ctx.tolerance\n                );\n            };\n\n            /**\n             * Main entry function to start a drag sequence.\n             */\n            const dragStart = () => {\n                state.dragging = true;\n                state.willDrag = false;\n\n                // Compute scrollable parent\n                const isDocumentScrollingElement =\n                    ctx.current.container === ctx.current.container.ownerDocument.scrollingElement;\n                // If the container is the \"ownerDocument.scrollingElement\",\n                // there is no need to get the scroll parent as it is the\n                // scrollable element itself.\n                // TODO: investigate if \"getScrollParents\" should not consider\n                // the \"ownerDocument.scrollingElement\" directly.\n                [ctx.current.scrollParentX, ctx.current.scrollParentY] = isDocumentScrollingElement\n                    ? [ctx.current.container, ctx.current.container]\n                    : getScrollParents(ctx.current.container);\n\n                updateRects();\n                const { x, y, width, height } = ctx.current.elementRect;\n\n                // Adjusts the offset\n                ctx.current.offset = {\n                    x: ctx.current.initialPosition.x - x,\n                    y: ctx.current.initialPosition.y - y,\n                };\n\n                if (ctx.followCursor) {\n                    dom.addStyle(ctx.current.element, {\n                        width: `${width}px`,\n                        height: `${height}px`,\n                        // Limit the impact of width and height !important on the dragged element\n                        \"max-width\": `${width}px`,\n                        \"max-height\": `${height}px`,\n                        position: \"fixed !important\",\n                    });\n\n                    // First adjustment\n                    updateElementPosition();\n                }\n\n                dom.addClass(document.body, \"pe-none\", \"user-select-none\");\n                if (params.iframeWindow) {\n                    for (const iframe of document.getElementsByTagName(\"iframe\")) {\n                        if (iframe.contentWindow === params.iframeWindow) {\n                            dom.addClass(iframe, \"pe-none\", \"user-select-none\");\n                        }\n                    }\n                }\n                // FIXME: adding pe-none and cursor on the same element makes\n                // no sense as pe-none prevents the cursor to be displayed.\n                if (ctx.cursor) {\n                    dom.addStyle(document.body, { cursor: ctx.cursor });\n                }\n\n                if (\n                    (ctx.current.scrollParentX || ctx.current.scrollParentY) &&\n                    ctx.edgeScrolling.enabled\n                ) {\n                    const cleanupFn = setRecurringAnimationFrame(handleEdgeScrolling);\n                    cleanup.add(cleanupFn);\n                }\n\n                dom.addClass(ctx.current.element, DRAGGED_CLASS);\n\n                callBuildHandler(\"onDragStart\");\n            };\n\n            /**\n             * Main exit function to stop a drag sequence. Note that it can be called\n             * even if a drag sequence did not start yet to perform a cleanup of all\n             * current context variables.\n             * @param {HTMLElement | null} target\n             * @param {boolean} [inErrorState] can be set to true when an error\n             *  occurred to avoid falling into an infinite loop if the error\n             *  originated from one of the handlers.\n             */\n            const dragEnd = (target, inErrorState) => {\n                if (state.dragging) {\n                    preventClick = true;\n                    if (!inErrorState) {\n                        if (\n                            target &&\n                            (params.allowDisconnected || ctx.current.element.isConnected)\n                        ) {\n                            callBuildHandler(\"onDrop\", { target });\n                        }\n                        callBuildHandler(\"onDragEnd\");\n                    }\n                }\n\n                cleanup.cleanup();\n            };\n\n            /**\n             * Applies scroll to the container if the current element is near\n             * the edge of the container.\n             */\n            const handleEdgeScrolling = (deltaTime) => {\n                updateRects();\n                const { x: pointerX, y: pointerY } = ctx.pointer;\n                const xRect = ctx.current.scrollParentXRect;\n                const yRect = ctx.current.scrollParentYRect;\n\n                // \"getBoundingClientRect()\"\" (used in \"getRect()\") gives the\n                // distance from the element's top to the viewport, excluding\n                // scroll position. Only the \"document.scrollingElement\" element\n                // (\"<html>\") accounts for scrollTop.\n                const scrollParentYEl = ctx.current.scrollParentY;\n                if (scrollParentYEl === ctx.current.container.ownerDocument.scrollingElement) {\n                    yRect.y += scrollParentYEl.scrollTop;\n                }\n\n                const { direction, speed, threshold } = ctx.edgeScrolling;\n                const correctedSpeed = (speed / 16) * deltaTime;\n\n                const diff = {};\n                ctx.current.scrollingEdge = null;\n                if (xRect) {\n                    const maxWidth = xRect.x + xRect.width;\n                    if (pointerX - xRect.x < threshold) {\n                        diff.x = [pointerX - xRect.x, -1];\n                        ctx.current.scrollingEdge = \"left\";\n                    } else if (maxWidth - pointerX < threshold) {\n                        diff.x = [maxWidth - pointerX, 1];\n                        ctx.current.scrollingEdge = \"right\";\n                    }\n                }\n                if (yRect) {\n                    const maxHeight = yRect.y + yRect.height;\n                    if (pointerY - yRect.y < threshold) {\n                        diff.y = [pointerY - yRect.y, -1];\n                        ctx.current.scrollingEdge = \"top\";\n                    } else if (maxHeight - pointerY < threshold) {\n                        diff.y = [maxHeight - pointerY, 1];\n                        ctx.current.scrollingEdge = \"bottom\";\n                    }\n                }\n\n                const diffToScroll = ([delta, sign]) =>\n                    (1 - Math.max(delta, 0) / threshold) * correctedSpeed * sign;\n                if ((!direction || direction === \"vertical\") && diff.y) {\n                    ctx.current.scrollParentY.scrollBy({ top: diffToScroll(diff.y) });\n                }\n                if ((!direction || direction === \"horizontal\") && diff.x) {\n                    ctx.current.scrollParentX.scrollBy({ left: diffToScroll(diff.x) });\n                }\n                callBuildHandler(\"onDrag\");\n            };\n\n            /**\n             * Global (= ref) \"click\" event handler.\n             * Used to prevent click events after dragEnd\n             * @param {PointerEvent} ev\n             */\n            const onClick = (ev) => {\n                if (preventClick) {\n                    safePrevent(ev, { stop: true });\n                }\n            };\n\n            /**\n             * Window \"keydown\" event handler.\n             * @param {KeyboardEvent} ev\n             */\n            const onKeyDown = (ev) => {\n                if (!state.dragging || !ctx.enable()) {\n                    return;\n                }\n                if (!WHITE_LISTED_KEYS.includes(ev.key)) {\n                    safePrevent(ev, { stop: true });\n\n                    // Cancels drag sequences on every non-whitelisted key down event.\n                    dragEnd(null);\n                }\n            };\n\n            /**\n             * Global (= ref) \"pointercancel\" event handler.\n             */\n            const onPointerCancel = () => {\n                dragEnd(null);\n            };\n\n            /**\n             * Global (= ref) \"pointerdown\" event handler.\n             * @param {PointerEvent} ev\n             */\n            const onPointerDown = (ev) => {\n                preventClick = false;\n                updatePointerPosition(ev);\n\n                const initiationDelay = ev.pointerType === \"touch\" ? ctx.touchDelay : ctx.delay;\n\n                // A drag sequence can still be in progress if the pointerup occurred\n                // outside of the window.\n                dragEnd(null);\n\n                const fullSelectorEl = ev.target.closest(ctx.fullSelector);\n                if (\n                    ev.button !== LEFT_CLICK ||\n                    !ctx.enable() ||\n                    !fullSelectorEl ||\n                    (ctx.ignoreSelector && ev.target.closest(ctx.ignoreSelector)) ||\n                    ctx.preventDrag(fullSelectorEl)\n                ) {\n                    return;\n                }\n\n                // In FireFox: elements with `overflow: hidden` will prevent mouseenter and mouseleave\n                // events from firing on elements underneath them. This is the case when dragging a card\n                // by the heading. In such cases, we can prevent the default\n                // action on the pointerdown event to allow pointer events to fire properly.\n                // https://bugzilla.mozilla.org/show_bug.cgi?id=1352061\n                // https://bugzilla.mozilla.org/show_bug.cgi?id=339293\n                safePrevent(ev);\n                ev.target.focus();\n                let activeElement = document.activeElement;\n                while (activeElement?.nodeName === \"IFRAME\") {\n                    activeElement = activeElement.contentDocument?.activeElement;\n                }\n                if (activeElement && !activeElement.contains(ev.target)) {\n                    activeElement.blur();\n                }\n\n                const { currentTarget, pointerId, target } = ev;\n                ctx.current.initialPosition = { ...ctx.pointer };\n\n                if (target.hasPointerCapture(pointerId)) {\n                    target.releasePointerCapture(pointerId);\n                }\n\n                if (initiationDelay) {\n                    if (hasTouch()) {\n                        if (ev.pointerType === \"touch\") {\n                            dom.addClass(target.closest(ctx.elementSelector), \"o_touch_bounce\");\n                        }\n                        if (isBrowserFirefox()) {\n                            // On Firefox mobile, long-touch events trigger an unpreventable\n                            // context menu to appear. To prevent this, all linkes are removed\n                            // from the dragged elements during the drag sequence.\n                            const links = [...currentTarget.querySelectorAll(\"[href]\")];\n                            if (currentTarget.hasAttribute(\"href\")) {\n                                links.unshift(currentTarget);\n                            }\n                            for (const link of links) {\n                                dom.removeAttribute(link, \"href\");\n                            }\n                        }\n                        if (isIOS()) {\n                            // On Safari mobile, any image can be dragged regardless\n                            // of the 'user-select' property.\n                            for (const image of currentTarget.getElementsByTagName(\"img\")) {\n                                dom.setAttribute(image, \"draggable\", false);\n                            }\n                        }\n                    }\n\n                    ctx.current.timeout = browser.setTimeout(() => {\n                        ctx.current.initialPosition = { ...ctx.pointer };\n\n                        willStartDrag(target);\n\n                        const { x: px, y: py } = ctx.pointer;\n                        const { x, y, width, height } = dom.getRect(ctx.current.element);\n                        if (px < x || x + width < px || py < y || y + height < py) {\n                            // Pointer left the target\n                            // Note that the timeout is cleared in dragEnd\n                            dragEnd(null);\n                        }\n                    }, initiationDelay);\n                    cleanup.add(() => browser.clearTimeout(ctx.current.timeout));\n                } else {\n                    willStartDrag(target);\n                }\n            };\n\n            /**\n             * Window \"pointermove\" event handler.\n             * @param {PointerEvent} ev\n             */\n            const onPointerMove = (ev) => {\n                updatePointerPosition(ev);\n\n                if (!ctx.current.element || !ctx.enable()) {\n                    return;\n                }\n\n                safePrevent(ev);\n\n                if (!state.dragging) {\n                    if (!canStartDrag()) {\n                        return;\n                    }\n                    dragStart();\n                } else if (!params.allowDisconnected && !ctx.current.element.isConnected) {\n                    return dragEnd(null);\n                }\n\n                if (ctx.followCursor) {\n                    updateElementPosition();\n                }\n\n                callBuildHandler(\"onDrag\");\n            };\n\n            /**\n             * Window \"pointerup\" event handler.\n             * @param {PointerEvent} ev\n             */\n            const onPointerUp = (ev) => {\n                updatePointerPosition(ev);\n                dragEnd(ev.target);\n            };\n\n            /**\n             * Updates the position of the current dragged element according to\n             * the current pointer position.\n             */\n            const updateElementPosition = () => {\n                const { containerRect, element, elementRect, offset } = ctx.current;\n                const { width: ew, height: eh } = elementRect;\n                const { x: cx, y: cy, width: cw, height: ch } = containerRect;\n\n                // Updates the position of the dragged element.\n                dom.addStyle(element, {\n                    left: `${clamp(ctx.pointer.x - offset.x, cx, cx + cw - ew)}px`,\n                    top: `${clamp(ctx.pointer.y - offset.y, cy, cy + ch - eh)}px`,\n                });\n            };\n\n            /**\n             * Updates the current pointer position from a given event.\n             * @param {PointerEvent} ev\n             */\n            const updatePointerPosition = (ev) => {\n                ctx.pointer.x = ev.clientX;\n                ctx.pointer.y = ev.clientY;\n            };\n\n            const updateRects = () => {\n                const { current } = ctx;\n                const { container, element, scrollParentX, scrollParentY } = current;\n                // Container rect\n                current.containerRect = dom.getRect(container, { adjust: true });\n                // If the scrolling element is within an iframe and the draggable\n                // element is outside this iframe, the offsets must be computed taking\n                // into account the iframe.\n                let iframeOffsetX = 0;\n                let iframeOffsetY = 0;\n                const iframeEl = container.ownerDocument.defaultView.frameElement;\n                if (iframeEl && !iframeEl.contentDocument?.contains(element)) {\n                    const { x, y } = dom.getRect(iframeEl);\n                    iframeOffsetX = x;\n                    iframeOffsetY = y;\n                    current.containerRect.x += iframeOffsetX;\n                    current.containerRect.y += iframeOffsetY;\n                }\n                // Adjust container rect according to its overflowing size\n                current.containerRect.width = container.scrollWidth;\n                current.containerRect.height = container.scrollHeight;\n                // ScrollParent rect\n                current.scrollParentXRect = null;\n                current.scrollParentYRect = null;\n                if (ctx.edgeScrolling.enabled) {\n                    // Adjust container rect according to scrollParents\n                    if (scrollParentX) {\n                        current.scrollParentXRect = dom.getRect(scrollParentX, { adjust: true });\n                        current.scrollParentXRect.x += iframeOffsetX;\n                        current.scrollParentXRect.y += iframeOffsetY;\n                        const right = Math.min(\n                            current.containerRect.left + container.scrollWidth,\n                            current.scrollParentXRect.right\n                        );\n                        current.containerRect.x = Math.max(\n                            current.containerRect.x,\n                            current.scrollParentXRect.x\n                        );\n                        current.containerRect.width = right - current.containerRect.x;\n                    }\n                    if (scrollParentY) {\n                        current.scrollParentYRect = dom.getRect(scrollParentY, { adjust: true });\n                        current.scrollParentYRect.x += iframeOffsetX;\n                        current.scrollParentYRect.y += iframeOffsetY;\n                        const bottom = Math.min(\n                            current.containerRect.top + container.scrollHeight,\n                            current.scrollParentYRect.bottom\n                        );\n                        current.containerRect.y = Math.max(\n                            current.containerRect.y,\n                            current.scrollParentYRect.y\n                        );\n                        current.containerRect.height = bottom - current.containerRect.y;\n                    }\n                }\n\n                // Element rect\n                ctx.current.elementRect = dom.getRect(element);\n            };\n\n            /**\n             * @param {Element} target\n             */\n            const willStartDrag = (target) => {\n                ctx.current.element = target.closest(ctx.elementSelector);\n                ctx.current.container = ctx.ref.el;\n\n                cleanup.add(() => (ctx.current = {}));\n                state.willDrag = true;\n\n                callBuildHandler(\"onWillStartDrag\");\n\n                if (hasTouch()) {\n                    // Prevents panning/zooming after a long press\n                    dom.addListener(window, \"touchmove\", safePrevent, {\n                        passive: false,\n                        noAddedStyle: true,\n                    });\n                    if (params.iframeWindow) {\n                        dom.addListener(params.iframeWindow, \"touchmove\", safePrevent, {\n                            passive: false,\n                            noAddedStyle: true,\n                        });\n                    }\n                }\n            };\n\n            // Initialize helpers\n            const cleanup = makeCleanupManager(() => (state.dragging = false));\n            const effectCleanup = makeCleanupManager();\n            const dom = makeDOMHelpers(cleanup);\n\n            const helpers = {\n                ...dom,\n                addCleanup: cleanup.add,\n                addEffectCleanup: effectCleanup.add,\n                callHandler,\n            };\n\n            // Component infos\n            const state = setupHooks.wrapState({ dragging: false });\n\n            // Basic error handling asserting that the parameters are valid.\n            for (const prop in allAcceptedParams) {\n                const type = typeof params[prop];\n                const acceptedTypes = allAcceptedParams[prop].map((t) => t.name.toLowerCase());\n                if (params[prop]) {\n                    if (!acceptedTypes.includes(type)) {\n                        throw makeError(\n                            `invalid type for property \"${prop}\" in parameters: expected { ${acceptedTypes.join(\n                                \", \"\n                            )} } and got ${type}`\n                        );\n                    }\n                } else if (MANDATORY_PARAMS.includes(prop) && !defaultParams[prop]) {\n                    throw makeError(`missing required property \"${prop}\" in parameters`);\n                }\n            }\n\n            /** @type {DraggableHookContext} */\n            const ctx = {\n                enable: () => false,\n                preventDrag: () => false,\n                ref: params.ref,\n                ignoreSelector: null,\n                fullSelector: null,\n                followCursor: true,\n                cursor: null,\n                pointer: { x: 0, y: 0 },\n                edgeScrolling: { enabled: true },\n                get dragging() {\n                    return state.dragging;\n                },\n                get willDrag() {\n                    return state.willDrag;\n                },\n                // Current context\n                current: {},\n            };\n\n            // Effect depending on the params to update them.\n            setupHooks.setup(\n                (...deps) => {\n                    const params = Object.fromEntries(deps);\n                    const actualParams = { ...defaultParams, ...omit(params, \"edgeScrolling\") };\n                    if (params.edgeScrolling) {\n                        actualParams.edgeScrolling = {\n                            ...actualParams.edgeScrolling,\n                            ...params.edgeScrolling,\n                        };\n                    }\n\n                    if (!ctx.ref.el) {\n                        return;\n                    }\n\n                    // Enable getter\n                    ctx.enable = actualParams.enable;\n\n                    // Dragging constraint\n                    if (actualParams.preventDrag) {\n                        ctx.preventDrag = actualParams.preventDrag;\n                    }\n\n                    // Selectors\n                    ctx.elementSelector = actualParams.elements;\n                    if (!ctx.elementSelector) {\n                        throw makeError(\n                            `no value found by \"elements\" selector: ${ctx.elementSelector}`\n                        );\n                    }\n                    const allSelectors = [ctx.elementSelector];\n                    ctx.cursor = actualParams.cursor || null;\n                    if (actualParams.handle) {\n                        allSelectors.push(actualParams.handle);\n                    }\n                    if (actualParams.ignore) {\n                        ctx.ignoreSelector = actualParams.ignore;\n                    }\n                    ctx.fullSelector = allSelectors.join(\" \");\n\n                    // Edge scrolling\n                    Object.assign(ctx.edgeScrolling, actualParams.edgeScrolling);\n\n                    // Delay & tolerance\n                    ctx.delay = actualParams.delay;\n                    ctx.touchDelay = actualParams.delay || actualParams.touchDelay;\n                    ctx.tolerance = actualParams.tolerance;\n\n                    callBuildHandler(\"onComputeParams\", { params: actualParams });\n\n                    // Calls effect cleanup functions when preparing to re-render.\n                    return effectCleanup.cleanup;\n                },\n                () => computeParams(params)\n            );\n            // Firefox currently (119.0.1) does not handle our pointer events\n            // nicely when they happen from within the iframe. To work around\n            // this, we use mouse events instead of pointer events.\n            const useMouseEvents = isBrowserFirefox() && !hasTouch() && params.iframeWindow;\n            // Effect depending on the `ref.el` to add triggering pointer events listener.\n            setupHooks.setup(\n                (el) => {\n                    if (el) {\n                        const { add, cleanup } = makeCleanupManager();\n                        const { addListener } = makeDOMHelpers({ add });\n                        const event = useMouseEvents ? \"mousedown\" : \"pointerdown\";\n                        addListener(el, event, onPointerDown, { noAddedStyle: true });\n                        addListener(el, \"click\", onClick);\n                        if (hasTouch()) {\n                            addListener(el, \"contextmenu\", safePrevent);\n                            // Adds a non-passive listener on touchstart: this allows\n                            // the subsequent \"touchmove\" events to be cancelable\n                            // and thus prevent parasitic \"touchcancel\" events to\n                            // be fired. Note that we DO NOT want to prevent touchstart\n                            // events since they're responsible of the native swipe\n                            // scrolling.\n                            addListener(el, \"touchstart\", () => {}, {\n                                passive: false,\n                                noAddedStyle: true,\n                            });\n                        }\n                        return cleanup;\n                    }\n                },\n                () => [ctx.ref.el]\n            );\n            const addWindowListener = (type, listener, options) => {\n                if (params.iframeWindow) {\n                    setupHooks.addListener(params.iframeWindow, type, listener, options);\n                }\n                setupHooks.addListener(window, type, listener, options);\n            };\n            // Other global event listeners.\n            const throttledOnPointerMove = setupHooks.throttle(onPointerMove);\n            addWindowListener(\n                useMouseEvents ? \"mousemove\" : \"pointermove\",\n                throttledOnPointerMove,\n                { passive: false }\n            );\n            addWindowListener(useMouseEvents ? \"mouseup\" : \"pointerup\", onPointerUp);\n            addWindowListener(\"pointercancel\", onPointerCancel);\n            addWindowListener(\"keydown\", onKeyDown, { capture: true });\n            setupHooks.teardown(() => dragEnd(null));\n\n            return state;\n        },\n    }[hookName];\n}\n", "import { onWillUnmount, reactive, useEffect, useExternalListener } from \"@odoo/owl\";\nimport { useThrottleForAnimation } from \"./timing\";\nimport { makeDraggableHook as nativeMakeDraggableHook } from \"./draggable_hook_builder\";\n\n/**\n * Set of default `makeDraggableHook` setup hooks that makes use of Owl lifecycle\n * and reactivity hooks to properly set up, update and tear down the elements and\n * listeners added by the draggable hook builder.\n *\n * @see {nativeMakeDraggableHook}\n * @type {typeof nativeMakeDraggableHook}\n */\nexport function makeDraggableHook(params) {\n    return nativeMakeDraggableHook({\n        ...params,\n        setupHooks: {\n            addListener: useExternalListener,\n            setup: useEffect,\n            teardown: onWillUnmount,\n            throttle: useThrottleForAnimation,\n            wrapState: reactive,\n        },\n    });\n}\n", "/**\n * Dynamic Viewport Units (DVU)\n *\n * Provides viewport measurement tools focusing on:\n * - Viewport change tracking for responsive components\n * - Viewport dimensions that respond to virtual keyboard\n *\n * Key differences between visualViewport and standard window dimensions:\n * - On mobile, when virtual keyboards appear, visualViewport.height decreases while\n *   innerHeight often doesn't\n * - During pinch-zoom on mobile, visualViewport dimensions change, while innerWidth/innerHeight\n *   remain static\n * - When mobile browser UI elements (address bars, toolbars) appear/disappear, visualViewport\n *   reflects these changes\n *\n * Enhanced with VirtualKeyboard API support:\n * - Reacts to the keyboard's appearance/disappearance via the geometrychange event\n * - Automatically updates viewport dimensions when keyboard visibility changes\n * - Triggers viewport change listeners when keyboard visibility changes\n *\n * The module will fall back to standard window dimensions when visualViewport API is not\n * available (primarily older browsers or some embedded webviews).\n *\n * References:\n * - https://www.w3.org/blog/CSS/2021/07/15/css-values-4-viewport-units/\n * - https://developer.mozilla.org/en-US/docs/Web/API/VirtualKeyboard_API\n */\n\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { onWillUnmount } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { isVirtualKeyboardSupported } from \"@web/core/browser/feature_detection\";\n\nconst viewport = {\n    listeners: [],\n\n    /**\n     * Register a callback for viewport changes\n     *\n     * @param {Function} listener - Function to call when viewport changes\n     * @returns {Function} - Function to remove the listener\n     */\n    addListener(listener) {\n        this.listeners.push(listener);\n        return () => {\n            const index = this.listeners.indexOf(listener);\n            if (index !== -1) {\n                this.listeners.splice(index, 1);\n            }\n        };\n    },\n\n    /**\n     * Notify all listeners of viewport changes\n     */\n    notifyListeners() {\n        this.listeners.forEach((listener) => listener());\n    },\n};\n\n// Initialize viewport tracking\nif (typeof window !== \"undefined\") {\n    const throttledUpdate = throttleForAnimation(() => viewport.notifyListeners());\n\n    if (browser.visualViewport) {\n        browser.visualViewport.addEventListener(\"resize\", throttledUpdate);\n    }\n\n    if (isVirtualKeyboardSupported()) {\n        browser.navigator.virtualKeyboard.addEventListener(\"geometrychange\", throttledUpdate);\n    }\n\n    // Fallback to window resize for browsers without VisualViewport or VirtualKeyboard\n    browser.addEventListener(\"resize\", throttledUpdate);\n}\n\n/**\n * Get current viewport dimensions\n * Takes into account VirtualKeyboard API if available\n *\n * @returns {Object} - Object with width and height properties in pixels\n */\nexport function getViewportDimensions() {\n    return {\n        width: browser.visualViewport?.width || browser.innerWidth,\n        height: browser.visualViewport?.height || browser.innerHeight,\n    };\n}\n\n/**\n * Register a callback for viewport dimension changes\n * This will trigger for regular viewport changes and virtual keyboard visibility changes\n *\n * @param {Function} callback - Function to call on viewport change\n * @returns {Function} - Function to remove the listener\n */\nexport function onViewportChange(callback) {\n    return viewport.addListener(callback);\n}\n\n/**\n * OWL hook to use viewport change tracking in components\n * Automatically cleans up listener when component is unmounted\n *\n * @param {Function} callback - Function to call when viewport changes\n */\nexport function useViewportChange(callback) {\n    const removeListener = onViewportChange(callback);\n    onWillUnmount(() => removeListener());\n}\n", "import { humanNumber } from \"@web/core/utils/numbers\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { session } from \"@web/session\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport const DEFAULT_MAX_FILE_SIZE = 128 * 1024 * 1024;\n\n/**\n * @param {Services[\"notification\"]} notificationService\n * @param {File} file\n * @param {Number} maxUploadSize\n * @returns {boolean}\n */\nexport function checkFileSize(fileSize, notificationService) {\n    const maxUploadSize = session.max_file_upload_size || DEFAULT_MAX_FILE_SIZE;\n    if (fileSize > maxUploadSize) {\n        notificationService.add(\n            _t(\n                \"The selected file (%(size)sB) is larger than the maximum allowed file size (%(maxSize)sB).\",\n                { size: humanNumber(fileSize), maxSize: humanNumber(maxUploadSize) }\n            ),\n            {\n                type: \"danger\",\n            }\n        );\n        return false;\n    }\n    return true;\n}\n\n/**\n * Hook to upload a file to the server.\n * @returns {function}\n */\nexport function useFileUploader() {\n    const http = useService(\"http\");\n    const notification = useService(\"notification\");\n    /**\n     * @param {string} route\n     * @param {Object} params\n     */\n    return async (route, params) => {\n        if ((params.ufile && params.ufile.length) || params.file) {\n            const fileSize = (params.ufile && params.ufile[0].size) || params.file.size;\n            if (!checkFileSize(fileSize, notification)) {\n                return null;\n            }\n        }\n        const fileData = await http.post(route, params, \"text\");\n        const parsedFileData = JSON.parse(fileData);\n        if (parsedFileData.error) {\n            throw new Error(parsedFileData.error);\n        }\n        return parsedFileData;\n    };\n}\n\nexport function resizeBlobImg(blob, params = {}) {\n    if (!blob.type || !blob.type.startsWith(\"image/\")) {\n        return Promise.reject(new Error(_t(\"The file is not an image, resizing is not possible\")));\n    }\n    const { width, height, offsetX, offsetY } = {\n        width: 256,\n        height: 256,\n        offsetX: 0.5,\n        offsetY: 0.5,\n        ...params,\n    };\n    return new Promise((resolve, reject) => {\n        const img = new Image();\n        img.onload = () => {\n            if (width < img.width || height < img.height) {\n                const canvas = document.createElement(\"canvas\");\n                canvas.width = width;\n                canvas.height = height;\n                const ctx = canvas.getContext(\"2d\");\n                ctx.imageSmoothingQuality = \"high\";\n                ctx.mozImageSmoothingEnabled = true;\n                ctx.webkitImageSmoothingEnabled = true;\n                ctx.msImageSmoothingEnabled = true;\n                ctx.imageSmoothingEnabled = true;\n\n                // Keep src image's aspect ratio\n                // while drawing in dest image with different ratio\n                const srcRatio = img.width / img.height;\n                const dWidth = Math.min(Math.floor(height * srcRatio), width);\n                const dHeight = Math.min(Math.floor(width / srcRatio), height);\n\n                // Start drawing at some proportion from the edges\n                // 0.5 means the image is centered on the image's shortest axis\n                const dx = Math.round((width - dWidth) * offsetX);\n                const dy = Math.round((height - dHeight) * offsetY);\n\n                ctx.drawImage(img, 0, 0, img.width, img.height, dx, dy, dWidth, dHeight);\n                canvas.toBlob(resolve);\n            } else {\n                resolve(blob);\n            }\n        };\n        img.onerror = () => {\n            reject(new Error(_t(\"The resizing of the image failed\")));\n        };\n        img.src = URL.createObjectURL(blob);\n    });\n}\n", "/**\n * Creates a version of the function that's memoized on the value of its first\n * argument, if any.\n *\n * @template T, U\n * @param {(arg: T) => U} func the function to memoize\n * @returns {(arg: T) => U} a memoized version of the original function\n */\nexport function memoize(func) {\n    const cache = new Map();\n    const funcName = func.name ? func.name + \" (memoized)\" : \"memoized\";\n    return {\n        [funcName](...args) {\n            if (!cache.has(args[0])) {\n                cache.set(args[0], func(...args));\n            }\n            return cache.get(...args);\n        },\n    }[funcName];\n}\n\n/**\n * Generate a unique integer id (unique within the entire client session).\n * Useful for temporary DOM ids.\n *\n * @param {string} prefix\n * @returns {string}\n */\nexport function uniqueId(prefix = \"\") {\n    return `${prefix}${++uniqueId.nextId}`;\n}\n// set nextId on the function itself to be able to patch then\nuniqueId.nextId = 0;\n", "import { hasTouch, isMobileOS } from \"@web/core/browser/feature_detection\";\n\nimport { status, useComponent, useEffect, useRef, onWillUnmount, useState, toRaw } from \"@odoo/owl\";\n\n/**\n * This file contains various custom hooks.\n * Their inner working is rather simple:\n * Each custom hook simply hooks itself to any number of owl lifecycle hooks.\n * You can then use them just like an owl hook in any Component\n * e.g.:\n * import { useBus } from \"@web/core/utils/hooks\";\n * ...\n * setup() {\n *    ...\n *    useBus(someBus, someEvent, callback)\n *    ...\n * }\n */\n\n/**\n * @typedef {{ readonly el: HTMLElement | null; }} Ref\n */\n\n// -----------------------------------------------------------------------------\n// useAutofocus\n// -----------------------------------------------------------------------------\n\n/**\n * Focus an element referenced by a t-ref=\"autofocus\" in the active component\n * as soon as it appears in the DOM and if it was not displayed before.\n * If it is an input/textarea, set the selection at the end.\n * @param {Object} [params]\n * @param {string} [params.refName] override the ref name \"autofocus\"\n * @param {boolean} [params.selectAll] if true, will select the entire text value.\n * @param {boolean} [params.mobile] if true, will force autofocus on touch devices.\n * @returns {Ref} the element reference\n */\nexport function useAutofocus({ refName, selectAll, mobile } = {}) {\n    const ref = useRef(refName || \"autofocus\");\n    const uiService = useService(\"ui\");\n\n    // Prevent autofocus on touch devices to avoid the virtual keyboard from popping up unexpectedly\n    if (!mobile && hasTouch()) {\n        return ref;\n    }\n    // LEGACY\n    if (!mobile && isMobileOS()) {\n        return ref;\n    }\n    function isFocusable(el) {\n        if (!el) {\n            return;\n        }\n        if (!uiService.activeElement || uiService.activeElement.contains(el)) {\n            return true;\n        }\n        const rootNode = el.getRootNode();\n        return rootNode instanceof ShadowRoot && uiService.activeElement.contains(rootNode.host);\n    }\n    // LEGACY\n    useEffect(\n        (el) => {\n            if (isFocusable(el)) {\n                el.focus();\n                if ([\"INPUT\", \"TEXTAREA\"].includes(el.tagName) && el.type !== \"number\") {\n                    el.selectionEnd = el.value.length;\n                    el.selectionStart = selectAll ? 0 : el.value.length;\n                }\n            }\n        },\n        () => [ref.el]\n    );\n    return ref;\n}\n\n// -----------------------------------------------------------------------------\n// useBus\n// -----------------------------------------------------------------------------\n\n/**\n * Ensures a bus event listener is attached and cleared the proper way.\n *\n * @param {import(\"@odoo/owl\").EventBus} bus\n * @param {string} eventName\n * @param {EventListener} callback\n */\nexport function useBus(bus, eventName, callback) {\n    const component = useComponent();\n    useEffect(\n        () => {\n            const listener = callback.bind(component);\n            bus.addEventListener(eventName, listener);\n            return () => bus.removeEventListener(eventName, listener);\n        },\n        () => []\n    );\n}\n\n// In an object so that it can be patched in tests (prevent error on blocking RPCs after tests)\nexport const useServiceProtectMethodHandling = {\n    fn() {\n        return this.original();\n    },\n    mocked() {\n        // Keep them unresolved so that no crash in test due to triggered RPCs by services\n        return new Promise(() => {});\n    },\n    original() {\n        return Promise.reject(new Error(\"Component is destroyed\"));\n    },\n};\n\n// -----------------------------------------------------------------------------\n// useService\n// -----------------------------------------------------------------------------\nfunction _protectMethod(component, fn) {\n    return function (...args) {\n        if (status(component) === \"destroyed\") {\n            return useServiceProtectMethodHandling.fn();\n        }\n\n        const prom = Promise.resolve(fn.call(this, ...args));\n        const protectedProm = prom.then((result) =>\n            status(component) === \"destroyed\" ? new Promise(() => {}) : result\n        );\n        return Object.assign(protectedProm, {\n            abort: prom.abort,\n            cancel: prom.cancel,\n        });\n    };\n}\n\nexport const SERVICES_METADATA = {};\n\n/**\n * Import a service into a component\n *\n * @template {keyof import(\"services\").ServiceFactories} K\n * @param {K} serviceName\n * @returns {import(\"services\").ServiceFactories[K]}\n */\nexport function useService(serviceName) {\n    const component = useComponent();\n    const { services } = component.env;\n    if (!(serviceName in services)) {\n        throw new Error(`Service ${serviceName} is not available`);\n    }\n    const service = services[serviceName];\n    if (SERVICES_METADATA[serviceName]) {\n        if (service instanceof Function) {\n            return _protectMethod(component, service);\n        } else {\n            const methods = SERVICES_METADATA[serviceName] ?? [];\n            const result = Object.create(service);\n            for (const method of methods) {\n                result[method] = _protectMethod(component, service[method]);\n            }\n            return result;\n        }\n    }\n    if (toRaw(service) !== service) {\n        return useState(service);\n    }\n    return service;\n}\n\n// -----------------------------------------------------------------------------\n// useSpellCheck\n// -----------------------------------------------------------------------------\n\n/**\n * To avoid elements to keep their spellcheck appearance when they are no\n * longer in focus. We only add this attribute when needed. To disable this\n * behavior, use the spellcheck attribute on the element.\n */\nexport function useSpellCheck({ refName } = {}) {\n    const elements = [];\n    const ref = useRef(refName || \"spellcheck\");\n    function toggleSpellcheck(ev) {\n        ev.target.spellcheck = document.activeElement === ev.target;\n    }\n    useEffect(\n        (el) => {\n            if (el) {\n                const inputs =\n                    [\"INPUT\", \"TEXTAREA\"].includes(el.nodeName) || el.isContentEditable\n                        ? [el]\n                        : el.querySelectorAll(\"input, textarea, [contenteditable=true]\");\n                inputs.forEach((input) => {\n                    if (input.spellcheck !== false) {\n                        elements.push(input);\n                        input.addEventListener(\"focus\", toggleSpellcheck);\n                        input.addEventListener(\"blur\", toggleSpellcheck);\n                    }\n                });\n            }\n            return () => {\n                elements.forEach((input) => {\n                    input.removeEventListener(\"focus\", toggleSpellcheck);\n                    input.removeEventListener(\"blur\", toggleSpellcheck);\n                });\n            };\n        },\n        () => [ref.el]\n    );\n}\n\n/**\n * @typedef {Function} ForwardRef\n * @property {HTMLElement | undefined} el\n */\n\n/**\n * Use a ref that was forwarded by a child @see useForwardRefToParent\n *\n * @returns {ForwardRef} a ref that can be called to set its value to that of a\n *  child ref, but can otherwise be used as a normal ref object\n */\nexport function useChildRef() {\n    let defined = false;\n    let value;\n    return function ref(v) {\n        value = v;\n        if (defined) {\n            return;\n        }\n        Object.defineProperty(ref, \"el\", {\n            get() {\n                return value.el;\n            },\n        });\n        defined = true;\n    };\n}\n/**\n * Forwards the given refName to the parent by calling the corresponding\n * ForwardRef received as prop. @see useChildRef\n *\n * @param {string} refName name of the ref to forward\n * @returns {Ref} the same ref that is forwarded to the\n *  parent\n */\nexport function useForwardRefToParent(refName) {\n    const component = useComponent();\n    const ref = useRef(refName);\n    if (component.props[refName]) {\n        component.props[refName](ref);\n    }\n    return ref;\n}\n/**\n * Use the dialog service while also automatically closing the dialogs opened\n * by the current component when it is unmounted.\n *\n * @returns {import(\"@web/core/dialog/dialog_service\").DialogServiceInterface}\n */\nexport function useOwnedDialogs() {\n    const dialogService = useService(\"dialog\");\n    const cbs = [];\n    onWillUnmount(() => {\n        cbs.forEach((cb) => cb());\n    });\n    const addDialog = (...args) => {\n        const close = dialogService.add(...args);\n        cbs.push(close);\n        return close;\n    };\n    return addDialog;\n}\n/**\n * Manages an event listener on a ref. Useful for hooks that want to manage\n * event listeners, especially more than one. Prefer using t-on directly in\n * components. If your hook only needs a single event listener, consider simply\n * returning it from the hook and letting the user attach it with t-on.\n *\n * @param {Ref} ref\n * @param {Parameters<typeof EventTarget.prototype.addEventListener>} listener\n */\nexport function useRefListener(ref, ...listener) {\n    useEffect(\n        (el) => {\n            el?.addEventListener(...listener);\n            return () => el?.removeEventListener(...listener);\n        },\n        () => [ref.el]\n    );\n}\n", "import { htmlEscape, markup } from \"@odoo/owl\";\n\nimport { formatList, normalizedMatches } from \"@web/core/l10n/utils\";\nimport { unique } from \"@web/core/utils/arrays\";\nimport { escapeRegExp, sprintf } from \"@web/core/utils/strings\";\n\nconst Markup = markup().constructor;\n\n/**\n * Safely creates a Document fragment from content. If content was flagged as safe HTML using\n * `markup` it is parsed as HTML. Otherwise it is escaped and parsed as text.\n *\n * @param {string|ReturnType<markup>} content\n */\nexport function createDocumentFragmentFromContent(content) {\n    return new document.defaultView.DOMParser().parseFromString(htmlEscape(content), \"text/html\");\n}\n\n/**\n * Safely creates an element with the given content. If content was flagged as safe HTML using\n * `markup` it is set as innerHTML. Otherwise it is set as text.\n *\n * @param {string} elementName\n * @param {string|ReturnType<markup>} content\n * @returns {Element}\n */\nexport function createElementWithContent(elementName, content) {\n    const element = document.createElement(elementName);\n    setElementContent(element, content);\n    return element;\n}\n\n/**\n * Returns a markuped version of the input text where\n * the query is highlighted using the input classes\n * if it is part of the text. Will normalize the query\n * for advanced symbols matching\n *\n * @param {string | ReturnType<markup>} query\n * @param {string | ReturnType<markup>} text\n * @param {string | ReturnType<markup>} classes\n * @returns {string | ReturnType<markup>}\n */\nexport function highlightText(query, text, classes) {\n    if (!query) {\n        return text;\n    }\n    const matches = unique(\n        normalizedMatches(text, query).map((m) =>\n            // normalizedMatch will remove Markup and return string matches\n            // so it is necessary to restore the removed Markup when needed\n            query instanceof Markup ? markup(m.match.toLowerCase()) : m.match.toLowerCase()\n        )\n    );\n    let result = text;\n    for (const match of matches) {\n        const regex = new RegExp(\n            `(?<!&[^;]{0,5})(${escapeRegExp(htmlEscape(match))})(?=(?:[^>]*<[^<]*>)*[^<>]*$)`,\n            \"ig\"\n        );\n        result = htmlReplace(result, regex, (_, match) => {\n            /**\n             * markup: text is a Markup object (either escaped inside htmlReplace or\n             * flagged safe), `match` is directly coming from this value,\n             * and the regex doesn't do anything crazy to unescape it.\n             */\n            match = markup(match);\n            return markup`<span class=\"${classes}\">${match}</span>`;\n        });\n    }\n    return result;\n}\n\n/**\n * Same behavior as formatList, but produces safe HTML. If the values are flagged as safe HTML using\n * `markup()` they are set as it is. Otherwise they are escaped.\n *\n * @param {Array<string|ReturnType<markup>>} list The array of values to format into a list.\n * @param {Object} [param0]\n * @param {string} [param0.localeCode] The locale to use (e.g. en-US).\n * @param {\"standard\"|\"standard-short\"|\"or\"|\"or-short\"|\"unit\"|\"unit-short\"|\"unit-narrow\"} [param0.style=\"standard\"] The style to format the list with.\n * @returns {ReturnType<markup>} The formatted list.\n */\nexport function htmlFormatList(list, ...args) {\n    return markup(\n        formatList(\n            Array.from(list, (val) => htmlEscape(val).toString()),\n            ...args\n        )\n    );\n}\n\n/**\n * Applies string replace on content and returns a markup result built for HTML.\n *\n * @param {string|ReturnType<markup>} content\n * @param {string | RegExp} search\n * @param {string} replacement\n * @returns {ReturnType<markup>}\n */\nexport function htmlReplace(content, search, replacement) {\n    if (search instanceof RegExp && !(replacement instanceof Function)) {\n        throw new Error(\"htmlReplace: replacement must be a function when search is a RegExp.\");\n    }\n    content = htmlEscape(content);\n    if (typeof search === \"string\" || search instanceof String) {\n        search = htmlEscape(search);\n    }\n    const safeReplacement =\n        replacement instanceof Function\n            ? (...args) => htmlEscape(replacement(...args))\n            : htmlEscape(replacement);\n    // markup: content and replacement are escaped (or markup), replace is considered safe\n    return markup(content.replace(search, safeReplacement));\n}\n\n/**\n * Applies string replaceAll on content and returns a markup result built for HTML.\n *\n * @param {string|ReturnType<markup>} content\n * @param {string | RegExp} search\n * @param {string|(match: string) => string|ReturnType<markup>} replacement\n * @returns {ReturnType<markup>}\n */\nexport function htmlReplaceAll(content, search, replacement) {\n    if (search instanceof RegExp && !(replacement instanceof Function)) {\n        throw new Error(\"htmlReplaceAll: replacement must be a function when search is a RegExp.\");\n    }\n    content = htmlEscape(content);\n    if (typeof search === \"string\" || search instanceof String) {\n        search = htmlEscape(search);\n    }\n    const safeReplacement =\n        replacement instanceof Function\n            ? (...args) => htmlEscape(replacement(...args))\n            : htmlEscape(replacement);\n    // markup: content and replacement are escaped (or markup), replaceAll is considered safe\n    return markup(content.replaceAll(search, safeReplacement));\n}\n\n/**\n * Applies list join on content and returns a markup result built for HTML.\n *\n * @param {Array<string|ReturnType<markup>>} args\n * @returns {ReturnType<markup>}\n */\nexport function htmlJoin(list, separator = \"\") {\n    // markup: args and separator are escaped (or markup), join is considered safe\n    return markup(list.map((arg) => htmlEscape(arg)).join(htmlEscape(separator)));\n}\n\n/**\n * Same behavior as sprintf, but produces safe HTML. If the string or values are flagged as safe HTML\n * using `markup()` they are set as it is. Otherwise they are escaped.\n *\n * @param {string} str The string with placeholders (%s) to insert values into.\n * @param  {...any} values Primitive values to insert in place of placeholders.\n * @returns {string|Markup}\n */\nexport function htmlSprintf(str, ...values) {\n    const valuesDict = values[0];\n    if (\n        valuesDict &&\n        Object.prototype.toString.call(valuesDict) === \"[object Object]\" &&\n        !(valuesDict instanceof Markup)\n    ) {\n        // markup: escaped base string and values (assuming sprintf itself is safe)\n        return markup(\n            sprintf(\n                htmlEscape(str).toString(),\n                Object.fromEntries(\n                    Object.entries(valuesDict).map(([key, value]) => [\n                        key,\n                        htmlEscape(value).toString(),\n                    ])\n                )\n            )\n        );\n    }\n    // markup: escaped base string and values (assuming sprintf itself is safe)\n    return markup(\n        sprintf(\n            htmlEscape(str).toString(),\n            values.map((value) => htmlEscape(value).toString())\n        )\n    );\n}\n\n/**\n * Applies string trim on content and returns a markup result built for HTML.\n *\n * @param {string|ReturnType<markup>} content\n * @returns {string|ReturnType<markup>}\n */\nexport function htmlTrim(content) {\n    content = htmlEscape(content);\n    // markup: content is escaped (or markup), trim is considered safe\n    return markup(content.trim());\n}\n\n/**\n * Checks if a html content is empty. If there are only formatting tags\n * with style attributes or a void content. Famous use case is\n * '<p style=\"...\" class=\"..\"><br></p>' added by some web editor(s).\n * Note that because the use of this method is limited, we ignore the cases\n * like there's one <img> tag in the content. In such case, even if it's the\n * actual content, we consider it empty.\n *\n * @param {string|ReturnType<markup>} content\n * @returns {boolean} true if no content found or if containing only formatting tags\n */\nexport function isHtmlEmpty(content = \"\") {\n    return createElementWithContent(\"div\", content).textContent.trim() === \"\";\n}\n\n/**\n * Format the text as follow:\n *      \\*\\*text\\*\\* => Put the text in bold.\n *      --text-- => Put the text in muted.\n *      \\`text\\` => Put the text in a rounded badge (bg-primary).\n *      \\n => Insert a breakline.\n *      \\t => Insert 4 spaces.\n *\n * @param {string|ReturnType<markup>} text\n * @returns {string|ReturnType<markup>} the formatted text\n */\nexport function odoomark(text) {\n    const replacements = [\n        [/\\n/g, () => markup`<br/>`],\n        [/\\t/g, () => markup`<span style=\"margin-left: 2em\"></span>`],\n        [\n            /\\*\\*(.+?)\\*\\*/g,\n            (_, bold) => {\n                /**\n                 * markup: text is a Markup object (either escaped inside htmlReplace or\n                 * flagged safe), `bold` is directly coming from this value,\n                 * and the regex doesn't do anything crazy to unescape it.\n                 */\n                markup(bold);\n                return markup`<b>${bold}</b>`;\n            },\n        ],\n        [\n            /--(.+?)--/g,\n            (_, muted) => {\n                /**\n                 * markup: text is a Markup object (either escaped inside htmlReplace or\n                 * flagged safe), `muted` is directly coming from this value,\n                 * and the regex doesn't do anything crazy to unescape it.\n                 */\n                muted = markup(muted);\n                return markup`<span class='text-muted'>${muted}</span>`;\n            },\n        ],\n        [\n            /&#x60;(.+?)&#x60;/g,\n            (_, tag) => {\n                /**\n                 * markup: text is a Markup object (either escaped inside htmlReplace or\n                 * flagged safe), `tag` is directly coming from this value,\n                 * and the regex doesn't do anything crazy to unescape it.\n                 */\n                tag = markup(tag);\n                return markup`<span class=\"o_tag position-relative d-inline-flex align-items-center mw-100 o_badge badge rounded-pill lh-1 o_tag_color_0\">${tag}</span>`;\n            },\n        ],\n    ];\n    for (const replacement of replacements) {\n        text = htmlReplaceAll(text, replacement[0], replacement[1]);\n    }\n    return text;\n}\n\n/**\n * Safely sets content on element. If content was flagged as safe HTML using `markup` it is set as\n * innerHTML. Otherwise it is set as text.\n *\n * @param {Element} element\n * @param {string|ReturnType<markup>} content\n */\nexport function setElementContent(element, content) {\n    if (content instanceof Markup) {\n        // innerHTML: content is markup\n        element.innerHTML = content;\n    } else {\n        element.textContent = content;\n    }\n}\n\nexport function isMarkup(content) {\n    return content instanceof Markup;\n}\n", "import { Mutex } from \"./concurrency\";\n\nconst VERSION_TABLE = \"__DBVersion__\";\nconst VERSION_KEY = \"__version__\";\n\nexport class IDBQuotaExceededError extends Error {}\n\nfunction formatStorageSize(size) {\n    const units = [\"b\", \"Kb\", \"Mb\", \"Gb\"];\n    while (size >= 1000 && units.length > 1) {\n        size /= 1000;\n        units.splice(0, 1);\n    }\n    return `${size.toFixed(2)}${units[0]}`;\n}\n\nexport class IndexedDB {\n    constructor(name, version) {\n        this.name = name;\n        this._tables = new Set([VERSION_TABLE]);\n        this.mutex = new Mutex();\n        this.mutex.exec(() => this._checkVersion(version));\n    }\n\n    // -------------------------------------------------------------------------\n    // Public\n    // -------------------------------------------------------------------------\n\n    /**\n     * Reads data from a given table.\n     *\n     * @param {string} table\n     * @param {string} key\n     * @returns Promise\n     */\n    async read(table, key) {\n        this._tables.add(table);\n        return this.execute((db) => {\n            if (db) {\n                return this._read(db, table, key);\n            }\n        });\n    }\n\n    /**\n     * Write data into the given table\n     *\n     * @param {string} table\n     * @param {string} key\n     * @param  {any} value\n     * @returns Promise\n     */\n    async write(table, key, value) {\n        this._tables.add(table);\n        return this.execute((db) => {\n            if (db) {\n                return this._write(db, table, key, value);\n            }\n        });\n    }\n\n    /**\n     * Invalidates a table, or the whole database.\n     *\n     * @param {string|Array} [table=null] if not given, the whole database is invalidated\n     * @returns Promise\n     */\n    async invalidate(tables = null) {\n        return this.execute((db) => {\n            if (db) {\n                return this._invalidate(db, typeof tables === \"string\" ? [tables] : tables);\n            }\n        });\n    }\n\n    /**\n     * Delete the whole database\n     *\n     * @returns Promise\n     */\n    async deleteDatabase() {\n        return this.mutex.exec(() => this._deleteDatabase(() => {}));\n    }\n\n    /**\n     * open the database and execute the callback with the db as parameter.\n     *\n     * @params {Function} callback\n     * @returns Promise\n     */\n    async execute(callback) {\n        return this.mutex.exec(() => this._execute(callback));\n    }\n\n    // -------------------------------------------------------------------------\n    // Protected\n    // -------------------------------------------------------------------------\n\n    async _deleteDatabase(callback) {\n        return new Promise((resolve) => {\n            const request = indexedDB.deleteDatabase(this.name);\n            request.onsuccess = () => {\n                Promise.resolve(callback()).then(resolve);\n            };\n            request.onerror = (event) => {\n                console.error(`IndexedDB delete error: ${event.target.error?.message}`);\n                Promise.resolve(callback()).then(resolve);\n            };\n        });\n    }\n\n    async _checkVersion(version) {\n        return new Promise((resolve) => {\n            this._execute((db) => {\n                if (db) {\n                    return this._read(db, VERSION_TABLE, VERSION_KEY);\n                }\n            }).then((currentVersion) => {\n                if (!currentVersion) {\n                    this._execute((db) => {\n                        if (db) {\n                            this._write(db, VERSION_TABLE, VERSION_KEY, version);\n                        }\n                    }).then(resolve);\n                } else if (currentVersion !== version) {\n                    this._deleteDatabase(() => {\n                        this._execute((db) => {\n                            if (db) {\n                                this._write(db, VERSION_TABLE, VERSION_KEY, version);\n                            }\n                        });\n                    }).then(resolve);\n                } else {\n                    resolve();\n                }\n            });\n        });\n    }\n\n    async _execute(callback, idbVersion) {\n        return new Promise((resolve, reject) => {\n            const request = indexedDB.open(this.name, idbVersion);\n            request.onupgradeneeded = (event) => {\n                const db = event.target.result;\n                const dbTables = new Set(db.objectStoreNames);\n                const newTables = this._tables.difference(dbTables);\n                newTables.forEach((table) => db.createObjectStore(table));\n            };\n            request.onsuccess = (event) => {\n                const db = event.target.result;\n                const dbTables = new Set(db.objectStoreNames);\n                const newTables = this._tables.difference(dbTables);\n                if (newTables.size !== 0) {\n                    db.close();\n                    const version = db.version + 1;\n                    return this._execute(callback, version).then(resolve);\n                }\n                Promise.resolve(callback(db))\n                    .then(resolve)\n                    .catch(async (e) => {\n                        if (e.name === \"QuotaExceededError\") {\n                            const { quota, usage } = await navigator.storage.estimate();\n                            console.error(\n                                `IndexedDB error: Quota Exceeded (${formatStorageSize(\n                                    usage\n                                )} out of ${formatStorageSize(quota)} used)`\n                            );\n                            reject(new IDBQuotaExceededError());\n                        } else {\n                            reject(e);\n                        }\n                    })\n                    .finally(() => db.close());\n            };\n            request.onerror = (event) => {\n                console.error(`IndexedDB error: ${event.target.error?.message}`);\n                Promise.resolve(callback()).then(resolve);\n            };\n        });\n    }\n\n    async _write(db, table, key, record) {\n        return new Promise((resolve, reject) => {\n            // AAB: do we care about write performance?\n            // Relaxed durability improves the write performances\n            // https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/\n            // https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability\n            const transaction = db.transaction(table, \"readwrite\", { durability: \"relaxed\" });\n            transaction.objectStore(table).put(record, key); // put to allow updates\n            transaction.onerror = (ev) => reject(ev.target.error); // firefox (DOMException)\n            transaction.onabort = (ev) => reject(ev.target.error); // chrome (QuotaExceededError)\n            transaction.oncomplete = resolve;\n\n            // Force the changes to be committed to the database asap\n            // https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/commit\n            transaction.commit();\n        });\n    }\n\n    async _invalidate(db, tables) {\n        return new Promise((resolve, reject) => {\n            const objectStoreNames = [...db.objectStoreNames].filter(\n                (table) => table !== VERSION_TABLE\n            );\n            tables = tables ? objectStoreNames.filter((t) => tables.includes(t)) : objectStoreNames;\n\n            if (tables.length === 0) {\n                return resolve();\n            }\n            // Relaxed durability improves the write performances\n            // https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/\n            // https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability\n            const transaction = db.transaction(tables, \"readwrite\", { durability: \"relaxed\" });\n            const proms = tables.map(\n                (table) =>\n                    new Promise((resolve) => {\n                        const objectStore = transaction.objectStore(table);\n                        const request = objectStore.clear();\n                        request.onsuccess = resolve;\n                    })\n            );\n            transaction.onerror = () => reject(transaction.error);\n            Promise.all(proms).then(resolve);\n\n            // Force the changes to be committed to the database asap\n            // https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/commit\n            transaction.commit();\n        });\n    }\n\n    async _read(db, table, key) {\n        return new Promise((resolve, reject) => {\n            const transaction = db.transaction(table, \"readonly\");\n            const objectStore = transaction.objectStore(table);\n            const r = objectStore.get(key);\n            r.onsuccess = () => resolve(r.result);\n            transaction.onerror = () => reject(transaction.error);\n        });\n    }\n}\n", "const eventHandledWeakMap = new WeakMap();\n/**\n * Returns whether the given event has been handled with the given markName.\n *\n * @param {Event} ev\n * @param {string} markName\n * @returns {boolean}\n */\nexport function isEventHandled(ev, markName) {\n    if (!eventHandledWeakMap.get(ev)) {\n        return false;\n    }\n    return eventHandledWeakMap.get(ev).includes(markName);\n}\n/**\n * Marks the given event as handled by the given markName. Useful to allow\n * handlers in the propagation chain to make a decision based on what has\n * already been done.\n *\n * @param {Event} ev\n * @param {string} markName\n */\nexport function markEventHandled(ev, markName) {\n    if (!eventHandledWeakMap.get(ev)) {\n        eventHandledWeakMap.set(ev, []);\n    }\n    eventHandledWeakMap.get(ev).push(markName);\n}\n", "import { localization } from \"@web/core/l10n/localization\";\nimport { makeDraggableHook } from \"@web/core/utils/draggable_hook_builder_owl\";\n\n/** @typedef {import(\"@web/core/utils/draggable_hook_builder\").DraggableHandlerParams} DraggableHandlerParams */\n/** @typedef {DraggableHandlerParams & { group: HTMLElement | null }} NestedSortableHandlerParams */\n\n/**\n * @typedef {import(\"./sortable\").SortableParams} NestedSortableParams\n *\n * OPTIONAL\n *\n * @property {(HTMLElement) => boolean} [preventDrag] function receiving a\n *  the current target for dragging (element) and returning a boolean, whether\n *  the element can be effectively dragged or not.\n * @property {boolean | () => boolean} [nest] whether elements are nested or not.\n * @property {string | () => string} [listTagName] type of lists (\"ul\" or \"ol\").\n * @property {number | () => number} [nestInterval] Horizontal distance needed to trigger\n * a change in the list hierarchy (i.e. changing parent when moving horizontally)\n * @property {number | () => number} [maxLevels] The maximum depth of nested items\n * the list can accept. If set to '0' the levels are unlimited. Default: 0\n * @property {(DraggableHookContext) => boolean} [isAllowed] You can specify a custom function\n * to verify if a drop location is allowed. return True by default\n * @property {boolean} [useElementSize] The placeholder use the dragged element size instead\n * of the small 8px lines. Default:false\n *\n * HANDLERS (also optional)\n *\n * @property {(params: MoveParams) => any} [onMove] called when the element has moved\n * (changed position) (@see MoveParams).\n */\n\n/**\n * @typedef MoveParams\n * @property {HTMLElement} element\n * @property {HTMLElement | null} group\n * @property {HTMLElement | null} previous\n * @property {HTMLElement | null} next\n * @property {HTMLElement | null} newGroup\n * @property {HTMLElement | null} parent\n * @property {HTMLElement} placeholder\n */\n\n/**\n * @typedef SortableState\n * @property {boolean} dragging\n */\n\n/** @type {(params: NestedSortableParams) => SortableState} */\nexport const useNestedSortable = makeDraggableHook({\n    name: \"useNestedSortable\",\n    acceptedParams: {\n        groups: [String, Function],\n        connectGroups: [Boolean, Function],\n        nest: [Boolean],\n        listTagName: [String],\n        nestInterval: [Number],\n        maxLevels: [Number],\n        isAllowed: [Function],\n        useElementSize: [Boolean],\n    },\n    defaultParams: {\n        connectGroups: false,\n        currentGroup: null,\n        cursor: \"grabbing\",\n        edgeScrolling: { speed: 20, threshold: 60 },\n        elements: \"li\",\n        groupSelector: null,\n        nest: false,\n        listTagName: \"ul\",\n        nestInterval: 15,\n        maxLevels: 0,\n        isAllowed: (ctx) => true,\n        useElementSize: false,\n    },\n\n    // Set the parameters.\n    onComputeParams({ ctx, params }) {\n        // Group selector\n        ctx.groupSelector = params.groups || null;\n        if (ctx.groupSelector) {\n            ctx.fullSelector = [ctx.groupSelector, ctx.fullSelector].join(\" \");\n        }\n        // Connection across groups\n        ctx.connectGroups = params.connectGroups;\n        // Nested elements\n        ctx.nest = params.nest;\n        // List tag name\n        ctx.listTagName = params.listTagName;\n        // Horizontal distance needed to trigger a change in the list hierarchy\n        // (i.e. changing parent when moving horizontally)\n        ctx.nestInterval = params.nestInterval;\n        ctx.isRTL = localization.direction === \"rtl\";\n        ctx.maxLevels = params.maxLevels || 0;\n        ctx.isAllowed = params.isAllowed ?? (() => true);\n        ctx.useElementSize = params.useElementSize;\n    },\n\n    // Set the current group and create the placeholder row that will take the\n    // place of the moving row.\n    onWillStartDrag({ ctx, addCleanup }) {\n        if (ctx.groupSelector) {\n            ctx.currentGroup = ctx.current.element.closest(ctx.groupSelector);\n            if (!ctx.connectGroups) {\n                ctx.current.container = ctx.currentGroup;\n            }\n        }\n\n        if (ctx.nest) {\n            ctx.prevNestX = ctx.pointer.x;\n        }\n        ctx.current.placeHolder = ctx.current.element.cloneNode(false);\n        ctx.current.placeHolder.removeAttribute(\"id\");\n        ctx.current.placeHolder.classList.add(\"w-100\", \"d-block\");\n        if (ctx.useElementSize) {\n            ctx.current.placeHolder.style.height = getComputedStyle(ctx.current.element).height;\n            ctx.current.placeHolder.classList.add(\"o_nested_sortable_placeholder_realsize\");\n        } else {\n            ctx.current.placeHolder.classList.add(\"o_nested_sortable_placeholder\");\n        }\n        addCleanup(() => ctx.current.placeHolder.remove());\n    },\n\n    // Make the placeholder take the place of the moving row, and add style on\n    // different elements to provide feedback that there is an ongoing dragging\n    // sequence.\n    onDragStart({ ctx, addStyle }) {\n        // Horizontal position which will be used to detect row changes when moving vertically, so that\n        // we do not need to be on the row to trigger row changes (only the vertical position matters).\n        // Nested rows are shorter than \"root\" rows, and do not start at the same horizontal position.\n        // However, every row ends at the same horizontal position. Therefore, we use the end of the\n        // current element - 1 as horizontal position.\n        ctx.selectorX = ctx.isRTL\n            ? ctx.current.elementRect.left + 1\n            : ctx.current.elementRect.right - 1;\n\n        // Placeholder is initially added right after the current element.\n        ctx.current.element.after(ctx.current.placeHolder);\n        addStyle(ctx.current.element, { opacity: 0.5 });\n\n        // Remove pointer-events style added by draggable_hook_builder and set\n        // it on the view elements instead as in our case we want to show the\n        // ctx.cursor style on the whole screen, not only in the ref el.\n        addStyle(document.body, { \"pointer-events\": \"auto\" });\n        addStyle(document.querySelector(\".o_navbar\"), { \"pointer-events\": \"none\" });\n        addStyle(document.querySelector(\".o_action_manager\"), { \"pointer-events\": \"none\" });\n        addStyle(ctx.current.container, { \"pointer-events\": \"auto\" });\n\n        // Calls \"onDragStart\" handler\n        return {\n            element: ctx.current.element,\n            group: ctx.currentGroup,\n        };\n    },\n    _getDeepestChildLevel(ctx, node, depth = 0) {\n        let result = 0;\n        const childSelector = `${ctx.listTagName} ${ctx.elementSelector}`;\n        for (const childNode of node.querySelectorAll(childSelector)) {\n            result = Math.max(this._getDeepestChildLevel(ctx, childNode, depth + 1), result);\n        }\n        return depth ? result + 1 : result;\n    },\n    _hasReachMaxAllowedLevel(ctx) {\n        if (!ctx.nest || ctx.maxLevels < 1) {\n            return false;\n        }\n        let level = this._getDeepestChildLevel(ctx, ctx.current.element);\n        let list = ctx.current.placeHolder.closest(ctx.listTagName);\n        while (list) {\n            level++;\n            list = list.parentNode.closest(ctx.listTagName);\n        }\n        return level > ctx.maxLevels;\n    },\n    _isAllowedNodeMove(ctx) {\n        return (\n            !this._hasReachMaxAllowedLevel(ctx) && ctx.isAllowed(ctx.current, ctx.elementSelector)\n        );\n    },\n    // Check if the cursor moved enough to trigger a move. If it did, move the\n    // placeholder accordingly.\n    onDrag({ ctx, callHandler }) {\n        const onMove = (prevPos) => {\n            if (!ctx.isAllowed(ctx.current, ctx.elementSelector)) {\n                ctx.current.placeHolder.classList.add(\"d-none\");\n                return;\n            } else if (this._hasReachMaxAllowedLevel(ctx)) {\n                // If the placeholder has reached its max allowed level, it is\n                // moved back to its previous position.\n                const previousSiblingEl = ctx.current.placeHolder\n                    .closest(ctx.listTagName)\n                    .closest(ctx.elementSelector);\n                previousSiblingEl.after(ctx.current.placeHolder);\n                return;\n            }\n            ctx.current.placeHolder.classList.remove(\"d-none\");\n            callHandler(\"onMove\", {\n                element: ctx.current.element,\n                previous: ctx.current.placeHolder.previousElementSibling,\n                next: ctx.current.placeHolder.nextElementSibling,\n                parent: ctx.nest\n                    ? ctx.current.placeHolder.parentElement.closest(ctx.elementSelector)\n                    : false,\n                group: ctx.currentGroup,\n                newGroup: ctx.connectGroups\n                    ? ctx.current.placeHolder.closest(ctx.groupSelector)\n                    : ctx.currentGroup,\n                prevPos,\n                placeholder: ctx.current.placeHolder,\n            });\n        };\n        /**\n         * Get the list element inside an element, or create one if it does not\n         * exists.\n         * @param {HTMLElement} el\n         * @return {HTMLElement} list\n         */\n        const getChildList = (el) => {\n            let list = el.querySelector(ctx.listTagName);\n            if (!list) {\n                list = document.createElement(ctx.listTagName);\n                el.appendChild(list);\n            }\n            return list;\n        };\n\n        const getPosition = (el) => {\n            return {\n                previous: el.previousElementSibling,\n                next: el.nextElementSibling,\n                parent: el.parentElement?.closest(ctx.elementSelector) || null,\n                group: ctx.groupSelector ? el.closest(ctx.groupSelector) : false,\n            };\n        };\n        const position = getPosition(ctx.current.placeHolder);\n\n        /** If nesting elements is allowed, horizontal moves may change the\n         * parent of the placeholder element (the placeholder does not move\n         * above or under an element, but it changes parent):\n         *\n         * - Moving to the left makes the placeholder a child of the previous\n         *   element up in the nested hierarchy, only if the placeholder is the\n         *   last child of its current parent:\n         *\n         *                    Allowed:\n         *    el                           el\n         *     \u2523 parent                     \u2523 parent\n         *     \u2503  \u2523 child           -->     \u2503  \u2517 child\n         *     \u2503  \u2517 placeholder             \u2523 placeholder\n         *     \u2517 el                         \u2517 el\n         *\n         *                  Not Allowed:\n         *    el                           el\n         *     \u2523 parent                     \u2523 parent\n         *     \u2503  \u2523 placeholder     -->     \u2523 p\u2503laceholder   <-- error\n         *     \u2503  \u2517 child                   \u2503  \u2517 child\n         *     \u2517 el                         \u2517 el\n         *\n         *\n         * - Moving to the right makes the placeholder the last child of the\n         * next element down in the nested hierarchy:\n         *\n         *    el                           el\n         *     \u2523 parent                    \u2523 parent\n         *     \u2503  \u2517 child           -->    \u2503  \u2523 child\n         *     \u2523 placeholder               \u2503  \u2517 placeholder\n         *     \u2517 el                        \u2517 el\n         */\n        if (ctx.nest) {\n            const xInterval = ctx.prevNestX - ctx.pointer.x;\n            if (ctx.nestInterval - (-1) ** ctx.isRTL * xInterval < 1) {\n                // Place placeholder after its parent in its parent's list only\n                // if the placeholder is the last child of its parent\n                // (ignoring the current element which is in the dom)\n                let nextElement = position.next;\n                if (nextElement === ctx.current.element) {\n                    nextElement = nextElement.nextElementSibling;\n                }\n                if (!nextElement) {\n                    const newSibling = position.parent;\n                    if (newSibling) {\n                        newSibling.after(ctx.current.placeHolder);\n                        onMove(position);\n                    }\n                }\n                // Recenter the pointer coordinates to this step\n                ctx.prevNestX = ctx.pointer.x;\n                return;\n            } else if (ctx.nestInterval + (-1) ** ctx.isRTL * xInterval < 1) {\n                // Place placeholder as the last child of its previous sibling,\n                // (ignoring the current element which is in the dom)\n                let parent = position.previous;\n                if (parent === ctx.current.element) {\n                    parent = parent.previousElementSibling;\n                }\n                if (parent && parent.matches(ctx.elementSelector)) {\n                    getChildList(parent).appendChild(ctx.current.placeHolder);\n                    onMove(position);\n                }\n                // Recenter the pointer coordinates to this step\n                ctx.prevNestX = ctx.pointer.x;\n                return;\n            }\n        }\n        const currentTop = ctx.pointer.y - ctx.current.offset.y;\n        const closestEl = document.elementFromPoint(ctx.selectorX, currentTop);\n        if (!closestEl) {\n            // Cursor outside of viewport\n            return;\n        }\n        const element = closestEl.closest(ctx.elementSelector);\n        // Vertical moves should move the placeholder element up or down.\n        if (element && element !== ctx.current.placeHolder) {\n            const elementPosition = getPosition(element);\n            const eRect = element.getBoundingClientRect();\n            const pos = ctx.current.placeHolder.compareDocumentPosition(element);\n            // Place placeholder before the hovered element in its parent's\n            // list. If the cursor is in the upper part of the element and\n            // if the placeholder is currently after or inside the hovered\n            // element. If the position is not allowed but nesting is allowed,\n            // place the placeholder as the last child of the previous sibling\n            // instead.\n            if (currentTop - eRect.y < 10) {\n                if (\n                    pos & Node.DOCUMENT_POSITION_PRECEDING &&\n                    (ctx.nest || elementPosition.parent === position.parent)\n                ) {\n                    element.before(ctx.current.placeHolder);\n                    onMove(position);\n                    // Recenter the pointer coordinates to this step\n                    ctx.prevNestX = ctx.pointer.x;\n                }\n            } else if (currentTop - eRect.y > 15 && pos === Node.DOCUMENT_POSITION_FOLLOWING) {\n                // Place placeholder after the hovered element in its parent's\n                // list if the cursor is not in the upper part of the\n                // element and if the placeholder is currently before the\n                // hovered element.\n                // If nesting is allowed and if the element has at least one\n                // child, place the placeholder above the first child of the\n                // hovered element instead.\n                if (ctx.nest) {\n                    const elementChildList = getChildList(element);\n                    if (elementChildList.querySelector(ctx.elementSelector)) {\n                        elementChildList.prepend(ctx.current.placeHolder);\n                        onMove(position);\n                    } else {\n                        element.after(ctx.current.placeHolder);\n                        onMove(position);\n                    }\n                    // Recenter the pointer coordinates to this step\n                    ctx.prevNestX = ctx.pointer.x;\n                } else if (elementPosition.parent === position.parent) {\n                    element.after(ctx.current.placeHolder);\n                    onMove(position);\n                }\n            }\n        } else {\n            const group = closestEl.closest(ctx.groupSelector);\n            if (group && group !== position.group && (ctx.nest || !position.parent)) {\n                if (\n                    group.compareDocumentPosition(position.group) ===\n                    Node.DOCUMENT_POSITION_PRECEDING\n                ) {\n                    getChildList(group).prepend(ctx.current.placeHolder);\n                    onMove(position);\n                } else {\n                    getChildList(group).appendChild(ctx.current.placeHolder);\n                    onMove(position);\n                }\n                // Recenter the pointer coordinates to this step\n                ctx.prevNestX = ctx.pointer.x;\n                callHandler(\"onGroupEnter\", { group, placeholder: ctx.current.placeHolder });\n                callHandler(\"onGroupLeave\", {\n                    group: position.group,\n                    placeholder: ctx.current.placeHolder,\n                });\n            }\n        }\n    },\n    // If the drop position is different from the starting position, run the\n    // onDrop handler from the parameters.\n    onDrop({ ctx }) {\n        if (!this._isAllowedNodeMove(ctx)) {\n            return;\n        }\n        const previous = ctx.current.placeHolder.previousElementSibling;\n        const next = ctx.current.placeHolder.nextElementSibling;\n        if (previous !== ctx.current.element && next !== ctx.current.element) {\n            return {\n                element: ctx.current.element,\n                group: ctx.currentGroup,\n                previous,\n                next,\n                newGroup: ctx.groupSelector && ctx.current.placeHolder.closest(ctx.groupSelector),\n                parent: ctx.current.placeHolder.parentElement.closest(ctx.elementSelector),\n                placeholder: ctx.current.placeHolder,\n            };\n        }\n    },\n    // Run the onDragEnd handler from the parameters.\n    onDragEnd({ ctx }) {\n        return {\n            element: ctx.current.element,\n            group: ctx.currentGroup,\n        };\n    },\n});\n", "import { localization as l10n } from \"@web/core/l10n/localization\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { intersperse } from \"@web/core/utils/strings\";\n\n/**\n * Returns value clamped to the inclusive range of min and max.\n *\n * @param {number} num\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nexport function clamp(num, min, max) {\n    return Math.max(Math.min(num, max), min);\n}\n\n/**\n * A function to create flexibly-numbered lists of integers, handy for each and map loops.\n * step defaults to 1.\n * Returns a list of integers from start (inclusive) to stop (exclusive), incremented (or decremented) by step.\n * @param {number} start default 0\n * @param {number} stop\n * @param {number} step default 1\n * @returns {number[]}\n */\nexport function range(start, stop, step = 1) {\n    const array = [];\n    const nsteps = Math.floor((stop - start) / step);\n    for (let i = 0; i < nsteps; i++) {\n        array.push(start + step * i);\n    }\n    return array;\n}\n\n/**\n * Returns `value` rounded with `precision`, minimizing IEEE-754 floating point\n * representation errors, and applying the tie-breaking rule selected with\n * `method`, by default \"HALF-UP\" (away from zero).\n *\n * @param {number} value the value to be rounded\n * @param {number} precision a precision parameter. eg: 0.01 rounds to two digits.\n * @param {\"HALF-UP\" | \"HALF-DOWN\" | \"HALF-EVEN\" | \"UP\" | \"DOWN\"} [method=\"HALF-UP\"] the rounding method used:\n *    - \"HALF-UP\" rounds to the closest number with ties going away from zero.\n *    - \"HALF-DOWN\" rounds to the closest number with ties going towards zero.\n *    - \"HALF-EVEN\" rounds to the closest number with ties going to the closest even number.\n *    - \"UP\" always rounds away from 0.\n *    - \"DOWN\" always rounds towards 0.\n */\nexport function roundPrecision(value, precision, method = \"HALF-UP\") {\n    if (!value) {\n        return 0;\n    } else if (!precision || precision < 0) {\n        precision = 1;\n    }\n    let roundingFactor = precision;\n    let normalize = (val) => val / roundingFactor;\n    let denormalize = (val) => val * roundingFactor;\n    // inverting small rounding factors reduces rounding errors\n    if (roundingFactor < 1) {\n        roundingFactor = invertFloat(roundingFactor);\n        [normalize, denormalize] = [denormalize, normalize];\n    }\n    const normalizedValue = normalize(value);\n    const sign = Math.sign(normalizedValue);\n    const epsilonMagnitude = Math.log2(Math.abs(normalizedValue));\n    const epsilon = Math.pow(2, epsilonMagnitude - 50);\n    let roundedValue;\n\n    switch (method) {\n        case \"DOWN\": {\n            roundedValue = Math.trunc(normalizedValue + sign * epsilon);\n            break;\n        }\n        case \"HALF-DOWN\": {\n            roundedValue = Math.round(normalizedValue - sign * epsilon);\n            break;\n        }\n        case \"HALF-UP\": {\n            roundedValue = Math.round(normalizedValue + sign * epsilon);\n            break;\n        }\n        case \"HALF-EVEN\": {\n            const integral = Math.floor(normalizedValue);\n            const remainder = Math.abs(normalizedValue - integral);\n            const isHalf = Math.abs(0.5 - remainder) < epsilon;\n            roundedValue = isHalf ? integral + (integral & 1) : Math.round(normalizedValue);\n            break;\n        }\n        case \"UP\": {\n            roundedValue = Math.trunc(normalizedValue + sign * (1 - epsilon));\n            break;\n        }\n        default: {\n            throw new Error(`Unknown rounding method: ${method}`);\n        }\n    }\n\n    return denormalize(roundedValue);\n}\n\nfunction formatFixedDecimals(value, decimals) {\n    const rounded = roundDecimals(value, decimals);\n    const [intPart, decPart = \"\"] = rounded.toString().split(\".\");\n    const paddedDecimals = decPart.padEnd(decimals, \"0\").slice(0, decimals);\n    return decimals === 0 ? intPart : `${intPart}.${paddedDecimals}`;\n}\n\nexport function roundDecimals(value, decimals) {\n    /**\n     * The following decimals introduce numerical errors:\n     * Math.pow(10, -4) = 0.00009999999999999999\n     * Math.pow(10, -5) = 0.000009999999999999999\n     *\n     * Such errors will propagate in roundPrecision and lead to inconsistencies between Python\n     * and JavaScript. To avoid this, we parse the scientific notation.\n     */\n    return roundPrecision(value, parseFloat(\"1e\" + -decimals));\n}\n\n/**\n * @param {number} value\n * @param {integer} decimals\n * @returns {boolean}\n */\nexport function floatIsZero(value, decimals) {\n    return value === 0 || roundDecimals(value, decimals) === 0;\n}\n\n/**\n * Inserts \"thousands\" separators in the provided number.\n *\n * @param {string} string representing integer number\n * @param {string} [thousandsSep=\",\"] the separator to insert\n * @param {number[]} [grouping=[]]\n *   array of relative offsets at which to insert `thousandsSep`.\n *   See `strings.intersperse` method.\n * @returns {string}\n */\nexport function insertThousandsSep(number, thousandsSep = \",\", grouping = []) {\n    const negative = number[0] === \"-\";\n    number = negative ? number.slice(1) : number;\n    return (negative ? \"-\" : \"\") + intersperse(number, grouping, thousandsSep);\n}\n\n/**\n * Format a number to a human readable format. For example, 3000 could become 3k.\n * Or massive number can use the scientific exponential notation.\n *\n * @param {number} number to format\n * @param {Object} [options] Options to format\n * @param {number} [options.decimals=0] number of decimals to use\n *    if minDigits > 1 is used and effective on the number then decimals\n *    will be shrunk to zero, to avoid displaying irrelevant figures ( 0.01 compared to 1000 )\n * @param {number} [options.minDigits=1]\n *    the minimum number of digits to preserve when switching to another\n *    level of thousands (e.g. with a value of '2', 4321 will still be\n *    represented as 4321 otherwise it will be down to one digit (4k))\n * @returns {string}\n */\nexport function humanNumber(number, options = { decimals: 0, minDigits: 1 }) {\n    const decimals = options.decimals || 0;\n    const minDigits = options.minDigits || 1;\n    const d2 = Math.pow(10, decimals);\n    const numberMagnitude = +number.toExponential().split(\"e+\")[1];\n    number = Math.round(number * d2) / d2;\n    // the case numberMagnitude >= 21 corresponds to a number\n    // better expressed in the scientific format.\n    if (numberMagnitude >= 21) {\n        // we do not use number.toExponential(decimals) because we want to\n        // avoid the possible useless O decimals: 1e.+24 preferred to 1.0e+24\n        number = Math.round(number * Math.pow(10, decimals - numberMagnitude)) / d2;\n        return `${number}e+${numberMagnitude}`;\n    }\n    // note: we need to call toString here to make sure we manipulate the resulting\n    // string, not an object with a toString method.\n    const unitSymbols = _t(\"kMGTPE\").toString();\n    const sign = Math.sign(number);\n    number = Math.abs(number);\n    let symbol = \"\";\n    for (let i = unitSymbols.length; i > 0; i--) {\n        const s = Math.pow(10, i * 3);\n        if (s <= number / Math.pow(10, minDigits - 1)) {\n            number = Math.round((number * d2) / s) / d2;\n            symbol = unitSymbols[i - 1];\n            break;\n        }\n    }\n    const { decimalPoint, grouping, thousandsSep } = l10n;\n\n    // determine if we should keep the decimals (we don't want to display 1,020.02k for 1020020)\n    const decimalsToKeep = number >= 1000 ? 0 : decimals;\n    number = sign * number;\n    const [integerPart, decimalPart] = formatFixedDecimals(number, decimalsToKeep).split(\".\");\n    const int = insertThousandsSep(integerPart, thousandsSep, grouping);\n    if (!decimalPart) {\n        return int + symbol;\n    }\n    return int + decimalPoint + decimalPart + symbol;\n}\n\n/**\n * Returns a string representing a float.  The result takes into account the\n * user settings (to display the correct decimal separator).\n *\n * @param {number} value the value that should be formatted\n * @param {Object} [options]\n * @param {number[]} [options.digits] the number of digits that should be used,\n *   instead of the default digits precision in the field.\n * @param {boolean} [options.humanReadable] if true, large numbers are formatted\n *   to a human readable format.\n * @param {string} [options.decimalPoint] decimal separating character\n * @param {string} [options.thousandsSep] thousands separator to insert\n * @param {number[]} [options.grouping] array of relative offsets at which to\n *   insert `thousandsSep`. See `insertThousandsSep` method.\n * @param {number} [options.decimals] used for humanNumber formmatter\n * @param {boolean} [options.trailingZeros=true] if false, the decimal part\n *   won't contain unnecessary trailing zeros.\n * @returns {string}\n */\nexport function formatFloat(value, options = {}) {\n    let precision;\n    if (options.digits && options.digits[1] !== undefined) {\n        precision = options.digits[1];\n    } else {\n        precision = 2;\n    }\n    if (floatIsZero(value, precision)) {\n        value = 0.0;\n    }\n    if (options.humanReadable) {\n        return humanNumber(value, options);\n    }\n    const grouping = options.grouping || l10n.grouping;\n    const thousandsSep = \"thousandsSep\" in options ? options.thousandsSep : l10n.thousandsSep;\n    const decimalPoint = \"decimalPoint\" in options ? options.decimalPoint : l10n.decimalPoint;\n    const formatted = formatFixedDecimals(value, precision).split(\".\");\n    formatted[0] = insertThousandsSep(formatted[0], thousandsSep, grouping);\n    if (options.trailingZeros === false && formatted[1]) {\n        formatted[1] = formatted[1].replace(/0+$/, \"\");\n    }\n    return formatted[1] ? formatted.join(decimalPoint) : formatted[0];\n}\n\nconst _INVERTDICT = Object.freeze({\n    1e-1: 1e1,\n    1e-2: 1e2,\n    1e-3: 1e3,\n    1e-4: 1e4,\n    1e-5: 1e5,\n    1e-6: 1e6,\n    1e-7: 1e7,\n    1e-8: 1e8,\n    1e-9: 1e9,\n    1e-10: 1e10,\n    2e-1: 5,\n    2e-2: 5e1,\n    2e-3: 5e2,\n    2e-4: 5e3,\n    2e-5: 5e4,\n    2e-6: 5e5,\n    2e-7: 5e6,\n    2e-8: 5e7,\n    2e-9: 5e8,\n    2e-10: 5e9,\n    5e-1: 2,\n    5e-2: 2e1,\n    5e-3: 2e2,\n    5e-4: 2e3,\n    5e-5: 2e4,\n    5e-6: 2e5,\n    5e-7: 2e6,\n    5e-8: 2e7,\n    5e-9: 2e8,\n    5e-10: 2e9,\n});\n\n/**\n * Invert a number with increased accuracy.\n *\n * @param {number} value\n * @returns {number}\n */\nexport function invertFloat(value) {\n    let res = _INVERTDICT[value];\n    if (res === undefined) {\n        const [coeff, expt] = value.toExponential().split(\"e\").map(Number.parseFloat);\n        res = Number.parseFloat(`${coeff}e${-expt}`) / Math.pow(coeff, 2);\n    }\n    return res;\n}\n", "/**\n * Shallow compares two objects.\n *\n * @template {unknown} T\n * @param {T} obj1\n * @param {T} obj2\n * @param {(a: T[keyof T], b: T[keyof T]) => boolean} [comparisonFn]\n */\nexport function shallowEqual(obj1, obj2, comparisonFn = (a, b) => a === b) {\n    if (obj1 !== Object(obj1) || obj2 !== Object(obj2)) {\n        return obj1 === obj2;\n    }\n    const obj1Keys = Reflect.ownKeys(obj1);\n    return (\n        obj1Keys.length === Reflect.ownKeys(obj2).length &&\n        obj1Keys.every((key) => comparisonFn(obj1[key], obj2[key]))\n    );\n}\n\n/**\n * Deeply compares two objects.\n *\n * @template {unknown} T\n * @param {T} obj1\n * @param {T} obj2\n */\nexport const deepEqual = (obj1, obj2) => shallowEqual(obj1, obj2, deepEqual);\n\n/**\n * Deep copies an object. As it relies on JSON this function as some limitations\n * - no support for circular objects\n * - no support for specific classes, that will at best be lost and at worst crash (Map, Set etc...)\n * @template T\n * @param {T} object An object that is fully JSON stringifiable\n * @return {T}\n */\nexport function deepCopy(object) {\n    return object && JSON.parse(JSON.stringify(object));\n}\n\n/**\n * Returns whether the given value is an object, i.e. an instance of the `Object`\n * class or of one of its direct subclass.\n *\n * Note: this may wrongly validate any object implementing a modified `toString`\n * explicitly returning `\"[object Object]\"`.\n *\n * @param {unknown} value\n * @returns {boolean}\n * @example\n *  // true\n *  isObject({ a: 1 });\n *  isObject(Object.create(null));\n * @example\n *  // false\n *  isObject([1, 2, 3]);\n *  isObject(new Map([[\"a\", 1]]));\n */\nexport function isObject(value) {\n    return Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\n/**\n * Returns a shallow copy of object with every property in properties removed\n * if present in object.\n *\n * @template T\n * @template {keyof T} K\n * @param {T} object\n * @param {...(K)} properties\n */\nexport function omit(object, ...properties) {\n    /** @type {Omit<T, K>} */\n    const result = {};\n    const propertiesSet = new Set(properties);\n    for (const key in object) {\n        if (!propertiesSet.has(key)) {\n            result[key] = object[key];\n        }\n    }\n    return result;\n}\n\n/**\n * @template T\n * @template {keyof T} K\n * @param {T} object\n * @param {...(K)} properties\n * @returns {Pick<T, K>}\n */\nexport function pick(object, ...properties) {\n    return Object.fromEntries(\n        properties.filter((prop) => prop in object).map((prop) => [prop, object[prop]])\n    );\n}\n\n/**\n * Deeply merges two objects, recursively combining properties.\n * Works like the spread operator but will merge nested objects.\n *\n * This function doesn't merge arrays.\n *\n * @param {Object} target - The target object to merge into.\n * @param {Object} extension - The extension to apply.\n * @returns {Object} - The merged object.\n *\n * @example\n * const target = { a: 1, b: { c: 2 } };\n * const source = { a: 2, b: { d: 3 } };\n * const output = deepMerge(target, source);\n * // output => { a: 2, b: { c: 2, d: 3 } }\n */\nexport function deepMerge(target, extension) {\n    if (!isObject(target) && !isObject(extension)) {\n        return;\n    }\n\n    target = target || {};\n    const output = Object.assign({}, target);\n    if (isObject(extension)) {\n        for (const key of Reflect.ownKeys(extension)) {\n            if (\n                key in target &&\n                isObject(extension[key]) &&\n                !Array.isArray(extension[key]) &&\n                typeof extension[key] !== \"function\"\n            ) {\n                output[key] = deepMerge(target[key], extension[key]);\n            } else {\n                Object.assign(output, { [key]: extension[key] });\n            }\n        }\n    }\n\n    return output;\n}\n", "/**\n *  @typedef {{\n *      originalProperties: Map<string, PropertyDescriptor>;\n *      skeleton: object;\n *      extensions: Set<object>;\n *  }} PatchDescription\n */\n\n/** @type {WeakMap<object, PatchDescription>} */\nconst patchDescriptions = new WeakMap();\n\n/**\n * Create or get the patch description for the given `objToPatch`.\n * @param {object} objToPatch\n * @returns {PatchDescription}\n */\nfunction getPatchDescription(objToPatch) {\n    if (!patchDescriptions.has(objToPatch)) {\n        patchDescriptions.set(objToPatch, {\n            originalProperties: new Map(),\n            skeleton: Object.create(Object.getPrototypeOf(objToPatch)),\n            extensions: new Set(),\n        });\n    }\n    return patchDescriptions.get(objToPatch);\n}\n\n/**\n * @param {object} objToPatch\n * @returns {boolean}\n */\nfunction isClassPrototype(objToPatch) {\n    // class A {}\n    // isClassPrototype(A) === false\n    // isClassPrototype(A.prototype) === true\n    // isClassPrototype(new A()) === false\n    // isClassPrototype({}) === false\n    return (\n        Object.hasOwn(objToPatch, \"constructor\") && objToPatch.constructor?.prototype === objToPatch\n    );\n}\n\n/**\n * Traverse the prototype chain to find a potential property.\n * @param {object} objToPatch\n * @param {string} key\n * @returns {object}\n */\nfunction findAncestorPropertyDescriptor(objToPatch, key) {\n    let descriptor = null;\n    let prototype = objToPatch;\n    do {\n        descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n        prototype = Object.getPrototypeOf(prototype);\n    } while (!descriptor && prototype);\n    return descriptor;\n}\n\n/**\n * Patch an object\n *\n * If the intent is to patch a class, don't forget to patch the prototype, unless\n * you want to patch static properties/methods.\n *\n * @template T\n * @template {Partial<T>} U\n * @param {T} objToPatch The object to patch\n * @param {U} extension The object containing the patched properties\n * @returns {() => void} Returns an unpatch function\n */\nexport function patch(objToPatch, extension) {\n    if (typeof extension === \"string\") {\n        throw new Error(\n            `Patch \"${extension}\": Second argument is not the patch name anymore, it should be the object containing the patched properties`\n        );\n    }\n\n    const description = getPatchDescription(objToPatch);\n    description.extensions.add(extension);\n\n    const properties = Object.getOwnPropertyDescriptors(extension);\n    for (const [key, newProperty] of Object.entries(properties)) {\n        const oldProperty = Object.getOwnPropertyDescriptor(objToPatch, key);\n        if (oldProperty) {\n            // Store the old property on the skeleton.\n            Object.defineProperty(description.skeleton, key, oldProperty);\n        }\n\n        if (!description.originalProperties.has(key)) {\n            // Keep a trace of original property (prop before first patch), useful for unpatching.\n            description.originalProperties.set(key, oldProperty);\n        }\n\n        if (isClassPrototype(objToPatch)) {\n            // A property is enumerable on POJO ({ prop: 1 }) but not on classes (class A {}).\n            // Here, we only check if we patch a class prototype.\n            newProperty.enumerable = false;\n        }\n\n        if ((newProperty.get && 1) ^ (newProperty.set && 1)) {\n            // get and set are defined together. If they are both defined\n            // in the previous descriptor but only one in the new descriptor\n            // then the other will be undefined so we need to apply the\n            // previous descriptor in the new one.\n            const ancestorProperty = findAncestorPropertyDescriptor(objToPatch, key);\n            newProperty.get = newProperty.get ?? ancestorProperty?.get;\n            newProperty.set = newProperty.set ?? ancestorProperty?.set;\n        }\n\n        // Replace the old property by the new one.\n        Object.defineProperty(objToPatch, key, newProperty);\n    }\n\n    // Sets the current skeleton as the extension's prototype to make\n    // `super` keyword working and then set extension as the new skeleton.\n    description.skeleton = Object.setPrototypeOf(extension, description.skeleton);\n\n    return () => {\n        // Remove the description to start with a fresh base.\n        patchDescriptions.delete(objToPatch);\n\n        for (const [key, property] of description.originalProperties) {\n            if (property) {\n                // Restore the original property on the `objToPatch` object.\n                Object.defineProperty(objToPatch, key, property);\n            } else {\n                // Or remove the property if it did not exist at first.\n                delete objToPatch[key];\n            }\n        }\n\n        // Re-apply the patches without the current one.\n        description.extensions.delete(extension);\n        for (const extension of description.extensions) {\n            patch(objToPatch, extension);\n        }\n    };\n}\n", "import { isMobileOS } from \"@web/core/browser/feature_detection\";\nimport { loadJS } from \"@web/core/assets\";\n\n/**\n * Until we have our own implementation of the /web/static/lib/pdfjs/web/viewer.{html,js,css}\n * (currently based on Firefox), this method allows us to hide the buttons that we do not want:\n * * All edit buttons\n * * \"Open File\"\n * * \"Current Page\" (\"#viewBookmark\")\n * * \"Download\" (Hidden on mobile device like Android, iOS, ... or via option)\n * * \"Print\" (Hidden on mobile device like Android, iOS, ... or via option)\n * * \"Presentation\" (via options)\n * * \"Rotation\" (via options)\n *\n * @link https://mozilla.github.io/pdf.js/getting_started/\n *\n * @param {Element} rootElement IFRAME DOM element of PDF.js viewer\n * @param {Object} options options to hide additional buttons\n * @param {boolean} options.hideDownload hide download button\n * @param {boolean} options.hidePrint hide print button\n * @param {boolean} options.hidePresentation hide presentation button\n * @param {boolean} options.hideRotation hide rotation button\n */\nexport function hidePDFJSButtons(rootElement, options = {}) {\n    const hiddenElements = [\n        \"#editorModeButtons\",\n        \"button#openFile\",\n        \"button#secondaryOpenFile\",\n        \"a#viewBookmark\",\n        \"a#secondaryViewBookmark\",\n    ];\n    if (options.hideDownload || isMobileOS()) {\n        hiddenElements.push([\"button#downloadButton\", \"button#secondaryDownload\"]);\n    }\n    if (options.hidePrint || isMobileOS()) {\n        hiddenElements.push([\"button#printButton\", \"button#secondaryPrint\"]);\n    }\n    if (options.hidePresentation) {\n        hiddenElements.push(\"button#presentationMode\");\n    }\n    if (options.hideRotation) {\n        hiddenElements.push(\"button#pageRotateCw\");\n        hiddenElements.push(\"button#pageRotateCcw\");\n    }\n    const cssStyle = document.createElement(\"style\");\n    cssStyle.rel = \"stylesheet\";\n    cssStyle.textContent = `${hiddenElements.join(\", \")} {\n    display: none !important;\n}`;\n    const iframe =\n        rootElement.tagName === \"IFRAME\" ? rootElement : rootElement.querySelector(\"iframe\");\n    if (iframe) {\n        if (!iframe.dataset.hideButtons) {\n            iframe.dataset.hideButtons = \"true\";\n            iframe.addEventListener(\"load\", (event) => {\n                if (iframe.contentDocument && iframe.contentDocument.head) {\n                    iframe.contentDocument.head.appendChild(cssStyle);\n                }\n            });\n        }\n    } else {\n        console.warn(\"No IFRAME found\");\n    }\n}\n\nexport async function loadPDFJSAssets() {\n    return Promise.all([\n        loadJS(\"/web/static/lib/pdfjs/build/pdf.js\"),\n        loadJS(\"/web/static/lib/pdfjs/build/pdf.worker.js\"),\n    ]);\n}\n", "import { reactive } from \"@odoo/owl\";\n\n/**\n * This class should be used as a base when creating a class that is intended to\n * be used within the reactivity system, it avoids a specific class of bug where\n * callbacks that capture `this` declared in the constructor would escape the\n * reactivity system and prevent the observers from being notified:\n *\n * const bus = new EventBus();\n * class MyClass {\n *   constructor() {\n *     this.counter = 0;\n *     bus.addEventListener(\"change\", () => this.counter++);\n *     //                                   ^ Will never be reactive, this mutation will be missed\n *   }\n * }\n * const myObj = reactive(new MyClass(bus), () => console.log(myObj.counter));\n * myObj.counter++; // logs 0;\n * bus.trigger(\"change\"); // logs nothing!\n * myObj.counter++; // logs 2. counter == 1 was missed.\n */\nexport class Reactive {\n    constructor() {\n        return reactive(this);\n    }\n}\n\n/**\n * Creates a side-effect that runs based on the content of reactive objects.\n *\n * @template {object[]} T\n * @param {(...args: [...T]) => X} cb callback for the effect\n * @param {[...T]} deps the reactive objects that the effect depends on\n */\nexport function effect(cb, deps) {\n    const reactiveDeps = reactive(deps, () => {\n        cb(...reactiveDeps);\n    });\n    cb(...reactiveDeps);\n}\n\n/**\n * Adds computed properties to a reactive object derived from multiples sources.\n *\n * @template {object} T\n * @template {object[]} U\n * @template {{[key: string]: (this: T, ...rest: [...U]) => unknown}} V\n * @param {T} obj the reactive object on which to add the computed\n * properties\n * @param {[...U]} sources the reactive objects which are needed to compute\n * the properties\n * @param {V} descriptor the object containing methods to compute the\n * properties\n * @returns {T & {[key in keyof V]: ReturnType<V[key]>}}\n */\nexport function withComputedProperties(obj, sources, descriptor) {\n    for (const [key, compute] of Object.entries(descriptor)) {\n        effect(\n            (obj, sources) => {\n                obj[key] = compute.call(obj, ...sources);\n            },\n            [obj, sources]\n        );\n    }\n    return obj;\n}\n", "import { App, blockDom, Component, markup } from \"@odoo/owl\";\nimport { getTemplate } from \"@web/core/templates\";\nimport { appTranslateFn } from \"@web/core/l10n/translation\";\n\nexport function renderToElement(template, context = {}) {\n    const el = render(template, context).firstElementChild;\n    if (el?.nextElementSibling) {\n        throw new Error(\n            `The rendered template '${template}' contains multiple root ` +\n                `nodes that will be ignored using renderToElement, you should ` +\n                `consider using renderToFragment or refactoring the template.`\n        );\n    }\n    el?.remove();\n    return el;\n}\n\nexport function renderToFragment(template, context = {}) {\n    const frag = document.createDocumentFragment();\n    for (const el of [...render(template, context).children]) {\n        frag.appendChild(el);\n    }\n    return frag;\n}\n\n/**\n * renders a template with an (optional) context and outputs it as a string\n *\n * @param {string} template\n * @param {Object} context\n * @returns string: the html of the template\n */\nexport function renderToString(template, context = {}) {\n    return render(template, context).innerHTML;\n}\nlet app;\nObject.defineProperty(renderToString, \"app\", {\n    get: () => {\n        if (!app) {\n            app = new App(Component, {\n                name: \"renderToString\",\n                getTemplate,\n                translatableAttributes: [\"data-tooltip\"],\n                translateFn: appTranslateFn,\n            });\n        }\n        return app;\n    },\n});\n\nfunction render(template, context = {}) {\n    const app = renderToString.app;\n    const templateFn = app.getTemplate(template);\n    const bdom = templateFn(context, {});\n    const div = document.createElement(\"div\");\n    blockDom.mount(bdom, div);\n    return div;\n}\n\n/**\n * renders a template with an (optional) context and returns a Markup string,\n * suitable to be inserted in a template with a t-out directive\n *\n * @param {string} template\n * @param {Object} context\n * @returns {ReturnType<markup>} the html of the template, as a markup string\n */\nexport function renderToMarkup(template, context = {}) {\n    return markup(renderToString(template, context));\n}\n", "export function isScrollableX(el) {\n    if (el.scrollWidth > el.clientWidth && el.clientWidth > 0) {\n        return couldBeScrollableX(el);\n    }\n    return false;\n}\n\nexport function couldBeScrollableX(el) {\n    if (el) {\n        const overflow = getComputedStyle(el).getPropertyValue(\"overflow-x\");\n        if (/\\bauto\\b|\\bscroll\\b/.test(overflow)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n * Get the closest horizontally scrollable for a given element.\n *\n * @param {HTMLElement} el\n * @returns {HTMLElement | null}\n */\nexport function closestScrollableX(el) {\n    if (!el) {\n        return null;\n    }\n    if (isScrollableX(el)) {\n        return el;\n    }\n    return closestScrollableX(el.parentElement);\n}\n\nexport function isScrollableY(el) {\n    if (el && el.scrollHeight > el.clientHeight && el.clientHeight > 0) {\n        return couldBeScrollableY(el);\n    }\n    return false;\n}\n\nexport function couldBeScrollableY(el) {\n    if (el) {\n        const overflow = getComputedStyle(el).getPropertyValue(\"overflow-y\");\n        if (/\\bauto\\b|\\bscroll\\b/.test(overflow)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n * Get the closest vertically scrollable for a given element.\n *\n * @param {HTMLElement} el\n * @returns {HTMLElement | null}\n */\nexport function closestScrollableY(el) {\n    if (!el) {\n        return null;\n    }\n    if (isScrollableY(el)) {\n        return el;\n    }\n    return closestScrollableY(el.parentElement);\n}\n\n/**\n * Ensures that `element` will be visible in its `scrollable`.\n *\n * @param {HTMLElement} element\n * @param {object} options\n * @param {HTMLElement} [options.scrollable] a scrollable area\n * @param {boolean} [options.isAnchor] states if the scroll is to an anchor\n * @param {string} [options.behavior] \"smooth\", \"instant\", \"auto\" <=> undefined\n *        @url https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo#behavior\n * @param {number} [options.offset] applies a vertical offset\n */\nexport function scrollTo(element, options = {}) {\n    const { behavior = \"auto\", isAnchor = false, offset = 0 } = options;\n    const scrollable = closestScrollableY(options.scrollable || element.parentElement);\n    if (!scrollable) {\n        return;\n    }\n\n    const scrollBottom = scrollable.getBoundingClientRect().bottom;\n    const scrollTop = scrollable.getBoundingClientRect().top;\n    const elementBottom = element.getBoundingClientRect().bottom;\n    const elementTop = element.getBoundingClientRect().top;\n\n    const scrollPromises = [];\n\n    if (elementBottom > scrollBottom && !isAnchor) {\n        // The scroll place the element at the bottom border of the scrollable\n        scrollPromises.push(\n            new Promise((resolve) => {\n                scrollable.addEventListener(\"scrollend\", () => resolve(), { once: true });\n            })\n        );\n\n        scrollable.scrollTo({\n            top:\n                scrollable.scrollTop +\n                elementTop -\n                scrollBottom +\n                Math.ceil(element.getBoundingClientRect().height) +\n                offset,\n            behavior,\n        });\n    } else if (elementTop < scrollTop || isAnchor) {\n        // The scroll place the element at the top of the scrollable\n        scrollPromises.push(\n            new Promise((resolve) => {\n                scrollable.addEventListener(\"scrollend\", () => resolve(), { once: true });\n            })\n        );\n\n        scrollable.scrollTo({\n            top: scrollable.scrollTop - scrollTop + elementTop + offset,\n            behavior,\n        });\n\n        if (options.isAnchor) {\n            // If the scrollable is within a scrollable, another scroll should be done\n            const parentScrollable = closestScrollableY(scrollable.parentElement);\n            if (parentScrollable) {\n                scrollPromises.push(\n                    scrollTo(scrollable, {\n                        behavior,\n                        isAnchor: true,\n                        scrollable: parentScrollable,\n                    })\n                );\n            }\n        }\n    }\n\n    return Promise.all(scrollPromises);\n}\n\nexport function compensateScrollbar(\n    el,\n    add = true,\n    isScrollElement = true,\n    cssProperty = \"padding-right\"\n) {\n    if (!el) {\n        return;\n    }\n    // Compensate scrollbar\n    const scrollableEl = isScrollElement ? el : closestScrollableY(el.parentElement);\n    if (!scrollableEl) {\n        return;\n    }\n    const isRTL = scrollableEl.classList.contains(\".o_rtl\");\n    if (isRTL) {\n        cssProperty = cssProperty.replace(\"right\", \"left\");\n    }\n    el.style.removeProperty(cssProperty);\n    if (!add) {\n        return;\n    }\n    const style = window.getComputedStyle(el);\n    // Round up to the nearest integer to be as close as possible to\n    // the correct value in case of browser zoom.\n    const borderLeftWidth = Math.ceil(parseFloat(style.borderLeftWidth.replace(\"px\", \"\")));\n    const borderRightWidth = Math.ceil(parseFloat(style.borderRightWidth.replace(\"px\", \"\")));\n    const bordersWidth = borderLeftWidth + borderRightWidth;\n    const newValue =\n        parseInt(style[cssProperty]) +\n        scrollableEl.offsetWidth -\n        scrollableEl.clientWidth -\n        bordersWidth;\n    el.style.setProperty(cssProperty, `${newValue}px`, \"important\");\n}\n\nexport function getScrollingElement(document = window.document) {\n    const baseScrollingElement = document.scrollingElement;\n    if (isScrollableY(baseScrollingElement)) {\n        return baseScrollingElement;\n    }\n    const bodyHeight = window.getComputedStyle(document.body).height;\n    for (const el of document.body.children) {\n        // Search for a body child which is at least as tall as the body\n        // and which has the ability to scroll if enough content in it. If\n        // found, suppose this is the top scrolling element.\n        if (bodyHeight - el.scrollHeight > 1.5) {\n            continue;\n        }\n        if (isScrollableY(el)) {\n            return el;\n        }\n    }\n    return baseScrollingElement;\n}\n\nexport function getScrollingTarget(scrollingElement = window.document) {\n    const document = scrollingElement.ownerDocument;\n    return scrollingElement === document.scrollingElement ? document.defaultView : scrollingElement;\n}\n", "import { normalize } from \"@web/core/l10n/utils\";\n\n/**\n * @param {string} pattern\n * @param {string|string[]} strs\n * @returns {number}\n */\nfunction match(pattern, strs) {\n    if (!Array.isArray(strs)) {\n        strs = [strs];\n    }\n    let globalScore = 0;\n    for (const str of strs) {\n        globalScore = Math.max(globalScore, _match(pattern, str));\n    }\n    return globalScore;\n}\n\n/**\n * This private function computes a score that represent the fact that the\n * string contains the pattern, or not\n *\n * - If the score is 0, the string does not contain the letters of the pattern in\n *   the correct order.\n * - if the score is > 0, it actually contains the letters.\n *\n * Better matches will get a higher score: consecutive letters are better,\n * and a match closer to the beginning of the string is also scored higher.\n *\n * @param {string} pattern\n * @param {string} str\n * @returns {number}\n */\nfunction _match(pattern, str) {\n    let totalScore = 0;\n    let currentScore = 0;\n    let patternIndex = 0;\n\n    pattern = normalize(pattern);\n    str = normalize(str);\n\n    const len = str.length;\n\n    for (let i = 0; i < len; i++) {\n        if (str[i] === pattern[patternIndex]) {\n            patternIndex++;\n            currentScore += 100 + currentScore - i / 200;\n        } else {\n            currentScore = 0;\n        }\n        totalScore = totalScore + currentScore;\n    }\n\n    return patternIndex === pattern.length ? totalScore : 0;\n}\n\n/**\n * Return a list of things that matches a pattern, ordered by their 'score' (\n * higher score first). An higher score means that the match is better. For\n * example, consecutive letters are considered a better match.\n *\n * @template T\n * @param {string} pattern\n * @param {T[]} list\n * @param {(element: T) => (string|string[])} fn\n * @returns {T[]}\n */\nexport function fuzzyLookup(pattern, list, fn) {\n    const results = [];\n    list.forEach((data) => {\n        const score = match(pattern, fn(data));\n        if (score > 0) {\n            results.push({ score, elem: data });\n        }\n    });\n\n    // we want better matches first\n    results.sort((a, b) => b.score - a.score);\n\n    return results.map((r) => r.elem);\n}\n\n// Does `pattern` fuzzy match `string`?\n/**\n * @param {string} pattern\n * @param {string} string\n * @returns {boolean}\n */\nexport function fuzzyTest(pattern, string) {\n    return _match(pattern, string) !== 0;\n}\n\n/**\n * Performs fuzzy matching using a Levenshtein distance algorithm\n * to find matches within an error margin between a pattern\n * and a list of words.\n *\n * If the pattern is found directly inside an item,\n * it's treated as a perfect match (score 0).\n * Otherwise, the `getScore` function calculates the distance\n * between the pattern and each candidate\n *\n * @param {string} pattern - The string to match.\n * @param {string[]} list - The list of strings to compare against the pattern.\n * @param {number} errorRatio - Controls how many errors can a word have depending of its length.\n * @returns {string[]} The list of the words that matches within a defined number of errors.\n */\nexport function fuzzyLevenshteinLookup(pattern, list, errorRatio = 3) {\n    // We limit the maximum number of errors depending on the word length\n    // to not have \"overcorrections\" into words that doesn't have anything\n    // in common with what the user typed\n    const maxNbrCorrection = Math.round(pattern.length / errorRatio);\n    const results = [];\n    list.forEach((candidate) => {\n        let score = -1;\n        if (candidate.includes(pattern)) {\n            score = 0;\n            results.push({ score, elem: pattern });\n        } else {\n            score = getLevenshteinScore(pattern, candidate);\n            if (score >= 0 && score <= maxNbrCorrection) {\n                results.push({ score, elem: candidate });\n            }\n        }\n    });\n    results.sort((a, b) => a.score - b.score);\n    return results.map((r) => r.elem);\n}\n\n\n/**\n * Computes the Levenshtein distance between two strings.\n *\n * @param {string} a\n * @param {string} b\n * @returns {number} The Levenshtein distance between `a` and `b`.\n */\nfunction getLevenshteinScore(a, b) {\n    let aLength = a.length;\n    let bLength = b.length;\n\n    let distanceMatrix = [];\n    for (let i = 0; i <= aLength; i++) {\n        distanceMatrix[i] = [];\n        for (let j = 0; j <= bLength; j++) {\n            distanceMatrix[i][j] = 0;\n        }\n    }\n\n    for (let i = 0; i <= aLength; i++) {\n        for (let j = 0; j <= bLength; j++) {\n            if (Math.min(i, j) === 0) {\n                distanceMatrix[i][j] = Math.max(i, j);\n            } else {\n                if (a[i - 1] === b[j - 1]) {\n                    distanceMatrix[i][j] = distanceMatrix[i - 1][j - 1];\n                } else {\n                    distanceMatrix[i][j] = Math.min(\n                        distanceMatrix[i - 1][j] + 1,\n                        distanceMatrix[i][j - 1] + 1,\n                        distanceMatrix[i - 1][j - 1] + 1\n                    );\n                }\n            }\n        }\n    }\n    return distanceMatrix[aLength][bLength];\n}\n", "import {\n    DRAGGED_CLASS,\n    makeDraggableHook as nativeMakeDraggableHook,\n} from \"@web/core/utils/draggable_hook_builder\";\nimport { pick } from \"@web/core/utils/objects\";\n\n/** @typedef {import(\"@web/core/utils/draggable_hook_builder\").DraggableHandlerParams} DraggableHandlerParams */\n/** @typedef {DraggableHandlerParams & { group: HTMLElement | null }} SortableHandlerParams */\n\n/**\n * @typedef SortableParams\n *\n * MANDATORY\n *\n * @property {{ el: HTMLElement | null }} ref\n * @property {string} elements defines sortable elements\n *\n * OPTIONAL\n *\n * @property {boolean | (() => boolean)} [enable] whether the sortable system should\n *  be enabled.\n * @property {number} [delay] delay before starting a sequence after a \"pointerdown\".\n * @property {number} [touchDelay] same as \"delay\", but specific to touch environments.\n * @property {string | (() => string)} [groups] defines parent groups of sortable\n *  elements. This allows to add `onGroupEnter` and `onGroupLeave` callbacks to\n *  work on group elements during the dragging sequence.\n * @property {string | (() => string)} [handle] additional selector for when the\n *  dragging sequence must be initiated when dragging on a certain part of the element.\n * @property {string | (() => string)} [ignore] selector targetting elements that\n *  must initiate a drag.\n * @property {boolean | (() => boolean)} [connectGroups] whether elements can be\n *  dragged accross different parent groups. Note that it requires a `groups` param to work.\n * @property {string | (() => string)} [cursor] cursor style during the dragging\n *  sequence.\n * @property {boolean} [clone] the placeholder is a clone of the drag element.\n * @property {string[]} [placeholderClasses] array of classes added to the placeholder\n *  element.\n * @property {boolean} [applyChangeOnDrop] on drop the change is applied to the DOM.\n * @property {string[]} [followingElementClasses] array of classes added to the\n *  element that follow the pointer.\n *\n * HANDLERS (also optional)\n *\n * @property {(params: SortableHandlerParams) => any} [onDragStart]\n *  called when a dragging sequence is initiated.\n * @property {(params: DraggableHandlerParams) => any} [onElementEnter] called when\n *  the cursor enters another sortable element.\n * @property {(params: DraggableHandlerParams) => any} [onElementLeave] called when\n *  the cursor leaves another sortable element.\n * @property {(params: SortableHandlerParams) => any} [onGroupEnter] (if a `groups`\n *  is specified): will be called when the cursor enters another group element.\n * @property {(params: SortableHandlerParams) => any} [onGroupLeave] (if a `groups`\n *  is specified): will be called when the cursor leaves another group element.\n * @property {(params: SortableHandlerParams) => any} [onDragEnd]\n *  called when the dragging sequence ends, regardless of the reason.\n * @property {(params: DropParams) => any} [onDrop] called when the dragging sequence\n *  ends on a pointerup action AND the dragged element has been moved elsewhere.\n *  The callback will be given an object with any useful element regarding the new\n *  position of the dragged element (@see DropParams ).\n */\n\n/**\n * @typedef DropParams\n * @property {HTMLElement} element\n * @property {HTMLElement | null} group\n * @property {HTMLElement | null} previous\n * @property {HTMLElement | null} next\n * @property {HTMLElement | null} parent\n */\n\n/**\n * @typedef SortableState\n * @property {boolean} dragging\n */\n\n/** @type SortableParams */\nconst hookParams = {\n    name: \"useSortable\",\n    acceptedParams: {\n        groups: [String, Function],\n        connectGroups: [Boolean, Function],\n        clone: [Boolean],\n        placeholderClasses: [Object],\n        applyChangeOnDrop: [Boolean],\n        followingElementClasses: [Object],\n    },\n    defaultParams: {\n        connectGroups: false,\n        edgeScrolling: { speed: 20, threshold: 60 },\n        groupSelector: null,\n        clone: true,\n        placeholderClasses: [],\n        applyChangeOnDrop: false,\n        followingElementClasses: [],\n    },\n\n    // Build steps\n    onComputeParams({ ctx, params }) {\n        // Group selector\n        ctx.groupSelector = params.groups || null;\n        if (ctx.groupSelector) {\n            ctx.fullSelector = [ctx.groupSelector, ctx.fullSelector].join(\" \");\n        }\n\n        // Connection accross groups\n        ctx.connectGroups = params.connectGroups;\n\n        ctx.placeholderClone = params.clone;\n        ctx.placeholderClasses = params.placeholderClasses;\n        ctx.applyChangeOnDrop = params.applyChangeOnDrop;\n        ctx.followingElementClasses = params.followingElementClasses;\n    },\n\n    // Runtime steps\n    onDragStart({ ctx, addListener, addStyle, callHandler }) {\n        /**\n         * Element \"pointerenter\" event handler.\n         * @param {PointerEvent} ev\n         */\n        const onElementPointerEnter = (ev) => {\n            const element = ev.currentTarget;\n            if (\n                connectGroups ||\n                !groupSelector ||\n                current.group === element.closest(groupSelector)\n            ) {\n                const pos = current.placeHolder.compareDocumentPosition(element);\n                if (pos === Node.DOCUMENT_POSITION_PRECEDING) {\n                    element.before(current.placeHolder);\n                } else if (pos === Node.DOCUMENT_POSITION_FOLLOWING) {\n                    element.after(current.placeHolder);\n                }\n            }\n            callHandler(\"onElementEnter\", { element });\n        };\n\n        /**\n         * Element \"pointerleave\" event handler.\n         * @param {PointerEvent} ev\n         */\n        const onElementPointerLeave = (ev) => {\n            const element = ev.currentTarget;\n            callHandler(\"onElementLeave\", { element });\n        };\n\n        const onElementComplexPointerEnter = (ev) => {\n            if (ctx.haveAlreadyChanged) {\n                return;\n            }\n            const element = ev.currentTarget;\n\n            const siblingArray = [...element.parentElement.children].filter(\n                (el) =>\n                    el === current.placeHolder ||\n                    (el.matches(elementSelector) && !el.classList.contains(DRAGGED_CLASS))\n            );\n            const elementIndex = siblingArray.indexOf(element);\n            const placeholderIndex = siblingArray.indexOf(current.placeHolder);\n            const isDirectSibling = Math.abs(elementIndex - placeholderIndex) === 1;\n            if (\n                connectGroups ||\n                !groupSelector ||\n                current.group === element.closest(groupSelector)\n            ) {\n                const pos = current.placeHolder.compareDocumentPosition(element);\n                if (isDirectSibling) {\n                    if (pos === Node.DOCUMENT_POSITION_PRECEDING) {\n                        element.before(current.placeHolder);\n                        ctx.haveAlreadyChanged = true;\n                    } else if (pos === Node.DOCUMENT_POSITION_FOLLOWING) {\n                        element.after(current.placeHolder);\n                        ctx.haveAlreadyChanged = true;\n                    }\n                } else {\n                    if (pos === Node.DOCUMENT_POSITION_FOLLOWING) {\n                        element.before(current.placeHolder);\n                        ctx.haveAlreadyChanged = true;\n                    } else if (pos === Node.DOCUMENT_POSITION_PRECEDING) {\n                        element.after(current.placeHolder);\n                        ctx.haveAlreadyChanged = true;\n                    }\n                }\n            }\n            callHandler(\"onElementEnter\", { element });\n        };\n\n        /**\n         * Element \"pointerleave\" event handler.\n         * @param {PointerEvent} ev\n         */\n        const onElementComplexPointerLeave = (ev) => {\n            if (ctx.haveAlreadyChanged) {\n                return;\n            }\n            const element = ev.currentTarget;\n            const elementRect = element.getBoundingClientRect();\n\n            const relatedElement = ev.relatedTarget;\n            const relatedElementRect = element.getBoundingClientRect();\n\n            const siblingArray = [...element.parentElement.children].filter(\n                (el) =>\n                    el === current.placeHolder ||\n                    (el.matches(elementSelector) && !el.classList.contains(DRAGGED_CLASS))\n            );\n            const pointerOnSiblings = siblingArray.indexOf(relatedElement) > -1;\n            const elementIndex = siblingArray.indexOf(element);\n            const isFirst = elementIndex === 0;\n            const isAbove = relatedElementRect.top <= elementRect.top;\n            const isLast = elementIndex === siblingArray.length - 1;\n            const isBelow = relatedElementRect.bottom >= elementRect.bottom;\n            const pos = current.placeHolder.compareDocumentPosition(element);\n            if (!pointerOnSiblings) {\n                if (isFirst && isAbove && pos === Node.DOCUMENT_POSITION_PRECEDING) {\n                    element.before(current.placeHolder);\n                    ctx.haveAlreadyChanged = true;\n                } else if (isLast && isBelow && pos === Node.DOCUMENT_POSITION_FOLLOWING) {\n                    element.after(current.placeHolder);\n                    ctx.haveAlreadyChanged = true;\n                }\n            }\n            callHandler(\"onElementLeave\", { element });\n        };\n\n        /**\n         * Group \"pointerenter\" event handler.\n         * @param {PointerEvent} ev\n         */\n        const onGroupPointerEnter = (ev) => {\n            const group = ev.currentTarget;\n            group.appendChild(current.placeHolder);\n            callHandler(\"onGroupEnter\", { group });\n        };\n\n        /**\n         * Group \"pointerleave\" event handler.\n         * @param {PointerEvent} ev\n         */\n        const onGroupPointerLeave = (ev) => {\n            const group = ev.currentTarget;\n            callHandler(\"onGroupLeave\", { group });\n        };\n\n        const { connectGroups, current, elementSelector, groupSelector, ref } = ctx;\n        if (ctx.placeholderClone) {\n            const { width, height } = current.elementRect;\n\n            // Adjusts size for the placeholder element\n            addStyle(current.placeHolder, {\n                visibility: \"hidden\",\n                display: \"block\",\n                width: `${width}px`,\n                height: `${height}px`,\n            });\n        }\n\n        // Binds handlers on eligible groups, if the elements are not confined to\n        // their parents and a 'groupSelector' has been provided.\n        if (connectGroups && groupSelector) {\n            for (const siblingGroup of ref.el.querySelectorAll(groupSelector)) {\n                addListener(siblingGroup, \"pointerenter\", onGroupPointerEnter);\n                addListener(siblingGroup, \"pointerleave\", onGroupPointerLeave);\n            }\n        }\n\n        // Binds handlers on eligible elements\n        for (const siblingEl of ref.el.querySelectorAll(elementSelector)) {\n            if (siblingEl !== current.element && siblingEl !== current.placeHolder) {\n                if (ctx.placeholderClone) {\n                    addListener(siblingEl, \"pointerenter\", onElementPointerEnter);\n                    addListener(siblingEl, \"pointerleave\", onElementPointerLeave);\n                } else {\n                    addListener(siblingEl, \"pointerenter\", onElementComplexPointerEnter);\n                    addListener(siblingEl, \"pointerleave\", onElementComplexPointerLeave);\n                }\n            }\n        }\n\n        // Placeholder is initially added right after the current element.\n        current.element.after(current.placeHolder);\n\n        return pick(current, \"element\", \"group\");\n    },\n    onDrag({ ctx }) {\n        ctx.haveAlreadyChanged = false;\n    },\n    onDragEnd({ ctx }) {\n        return pick(ctx.current, \"element\", \"group\");\n    },\n    onDrop({ ctx }) {\n        const { current, groupSelector } = ctx;\n        const previous = current.placeHolder.previousElementSibling;\n        const next = current.placeHolder.nextElementSibling;\n        if (previous !== current.element && next !== current.element) {\n            const element = current.element;\n            if (ctx.applyChangeOnDrop) {\n                // Apply to the DOM the result of sortable()\n                if (previous) {\n                    previous.after(element);\n                } else if (next) {\n                    next.before(element);\n                }\n            }\n            return {\n                element,\n                group: current.group,\n                previous,\n                next,\n                parent: groupSelector && current.placeHolder.closest(groupSelector),\n            };\n        }\n    },\n    onWillStartDrag({ ctx, addCleanup }) {\n        const { connectGroups, current, groupSelector } = ctx;\n\n        if (groupSelector) {\n            current.group = current.element.closest(groupSelector);\n            if (!connectGroups) {\n                current.container = current.group;\n            }\n        }\n\n        if (ctx.placeholderClone) {\n            current.placeHolder = current.element.cloneNode(false);\n        } else {\n            current.placeHolder = document.createElement(\"div\");\n        }\n        current.placeHolder.classList.add(...ctx.placeholderClasses);\n        current.element.classList.add(...ctx.followingElementClasses);\n\n        addCleanup(() => current.element.classList.remove(...ctx.followingElementClasses));\n        addCleanup(() => current.placeHolder.remove());\n\n        return pick(current, \"element\", \"group\");\n    },\n};\n\n/** @type {(params: SortableParams) => SortableState} */\nexport const useSortable = (sortableParams) => {\n    const { setupHooks } = sortableParams;\n    delete sortableParams.setupHooks;\n    return nativeMakeDraggableHook({ ...hookParams, setupHooks })(sortableParams);\n};\n", "import { onWillUnmount, reactive, useEffect, useExternalListener } from \"@odoo/owl\";\nimport { useThrottleForAnimation } from \"./timing\";\nimport { useSortable as nativeUseSortable } from \"@web/core/utils/sortable\";\n\n/**\n * Set of default `useSortable` setup hooks that makes use of Owl lifecycle\n * and reactivity hooks to properly set up, update and tear down the elements and\n * listeners added by the draggable hook builder.\n *\n * @see {nativeUseSortable}\n * @type {typeof nativeUseSortable}\n */\nexport function useSortable(params) {\n    return nativeUseSortable({\n        ...params,\n        setupHooks: {\n            addListener: useExternalListener,\n            setup: useEffect,\n            teardown: onWillUnmount,\n            throttle: useThrottleForAnimation,\n            wrapState: reactive,\n        },\n    });\n}\n", "import { registry } from \"../registry\";\nimport { useSortable } from \"@web/core/utils/sortable\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { reactive } from \"@odoo/owl\";\n\n/**\n * @typedef SortableServiceHookParams\n * @extends SortableParams\n * @property {{el: HTMLElement} | ReturnType<typeof import(\"@odoo/owl\").useRef>} [ref] container of sortable\n * @property {string | Symbol} [sortableId] identifier when multiple sortable on the same container\n */\n\nconst DEFAULT_SORTABLE_ID = Symbol.for(\"defaultSortable\");\nexport const sortableService = {\n    start() {\n        /**\n         * Map to avoid to setup/enable twice or more time the same element\n         * @type {Map<Element, Object>}\n         */\n        const boundElements = new Map();\n        return {\n            /**\n             * @param {SortableServiceHookParams} hookParams\n             */\n            create: (hookParams) => {\n                const element = hookParams.ref.el;\n                const sortableId = hookParams.sortableId ?? DEFAULT_SORTABLE_ID;\n                if (boundElements.has(element)) {\n                    const boundElement = boundElements.get(element);\n                    if (sortableId in boundElement) {\n                        return {\n                            enable() {\n                                return {\n                                    cleanup: boundElement[sortableId],\n                                };\n                            },\n                        };\n                    }\n                }\n                /**\n                 * @type {Map<Function, function():Array>}\n                 */\n                const setupFunctions = new Map();\n                /**\n                 * @type {Array<Function>}\n                 */\n                const cleanupFunctions = [];\n\n                const cleanup = () => {\n                    const boundElement = boundElements.get(element);\n                    if (sortableId in boundElement) {\n                        delete boundElement[sortableId];\n                        if (boundElement.length === 0) {\n                            boundElements.delete(element);\n                        }\n                    }\n                    cleanupFunctions.forEach((fn) => fn());\n                };\n\n                // Setup hookParam\n                const setupHooks = {\n                    wrapState: reactive,\n                    throttle: throttleForAnimation,\n                    addListener: (el, type, listener) => {\n                        el.addEventListener(type, listener);\n                        cleanupFunctions.push(() => el.removeEventListener(type, listener));\n                    },\n                    setup: (setupFn, dependenciesFn) => setupFunctions.set(setupFn, dependenciesFn),\n                    teardown: (fn) => cleanupFunctions.push(fn),\n                };\n\n                useSortable({ setupHooks, ...hookParams });\n\n                const boundElement = boundElements.get(element);\n                if (boundElement) {\n                    boundElement[sortableId] = cleanup;\n                } else {\n                    boundElements.set(element, { [sortableId]: cleanup });\n                }\n\n                return {\n                    enable() {\n                        setupFunctions.forEach((dependenciesFn, setupFn) =>\n                            setupFn(...dependenciesFn())\n                        );\n                        return {\n                            cleanup,\n                        };\n                    },\n                };\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"sortable\", sortableService);\n", "import { isObject } from \"./objects\";\n\nexport const nbsp = \"\\u00a0\";\n\n/**\n * Escapes a string for HTML.\n *\n * @param {string | number} [str] the string to escape\n * @returns {string} an escaped string\n */\nexport function escape(str) {\n    if (str === undefined) {\n        return \"\";\n    }\n    if (typeof str === \"number\") {\n        return String(str);\n    }\n    [\n        [\"&\", \"&amp;\"],\n        [\"<\", \"&lt;\"],\n        [\">\", \"&gt;\"],\n        [\"'\", \"&#x27;\"],\n        ['\"', \"&quot;\"],\n        [\"`\", \"&#x60;\"],\n    ].forEach((pairs) => {\n        str = String(str).replaceAll(pairs[0], pairs[1]);\n    });\n    return str;\n}\n\n/**\n * Escapes a string to use as a RegExp.\n * @url https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping\n *\n * @param {string} str\n * @returns {string} escaped string to use as a RegExp\n */\nexport function escapeRegExp(str) {\n    return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Intersperses ``separator`` in ``str`` at the positions indicated by\n * ``indices``.\n *\n * ``indices`` is an array of relative offsets (from the previous insertion\n * position, starting from the end of the string) at which to insert\n * ``separator``.\n *\n * There are two special values:\n *\n * ``-1``\n *   indicates the insertion should end now\n * ``0``\n *   indicates that the previous section pattern should be repeated (until all\n *   of ``str`` is consumed)\n *\n * @param {string} str\n * @param {number[]} indices\n * @param {string} separator\n * @returns {string}\n */\nexport function intersperse(str, indices, separator = \"\") {\n    separator = separator || \"\";\n    const result = [];\n    let last = str.length;\n    for (let i = 0; i < indices.length; ++i) {\n        let section = indices[i];\n        if (section === -1 || last <= 0) {\n            // Done with string, or -1 (stops formatting string)\n            break;\n        } else if (section === 0 && i === 0) {\n            // repeats previous section, which there is none => stop\n            break;\n        } else if (section === 0) {\n            // repeat previous section forever\n            //noinspection AssignmentToForLoopParameterJS\n            section = indices[--i];\n        }\n        result.push(str.substring(last - section, last));\n        last -= section;\n    }\n    const s = str.substring(0, last);\n    if (s) {\n        result.push(s);\n    }\n    return result.reverse().join(separator);\n}\n\n/**\n * Returns a string formatted using given values.\n * If the value is an object, its keys will replace `%(key)s` expressions.\n * If the values are a set of strings, they will replace `%s` expressions.\n * If no value is given, the string will not be formatted.\n *\n * @param {string} s\n * @param {any[]} values\n * @returns {string}\n */\nexport function sprintf(s, ...values) {\n    if (values.length === 1 && isObject(values[0])) {\n        const valuesDict = values[0];\n        s = s.replace(/%\\(([^)]+)\\)s/g, (match, value) => valuesDict[value]);\n    } else if (values.length > 0) {\n        s = s.replace(/%s/g, () => values.shift());\n    }\n    return s;\n}\n\n/**\n * Capitalizes a string: \"abc def\" => \"Abc def\"\n *\n * @param {string} s the input string\n * @returns {string}\n */\nexport function capitalize(s) {\n    return s ? s[0].toUpperCase() + s.slice(1) : \"\";\n}\n\n/**\n * @param {string} value\n * @returns boolean\n */\nexport function isEmail(value) {\n    // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript\n    const re =\n        // eslint-disable-next-line no-useless-escape\n        /^(([^<>()\\[\\]\\.,;:\\s@\\\"]+(\\.[^<>()\\[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$/i;\n    return re.test(value);\n}\n\n/**\n * Return true if the string is composed of only digits\n *\n * @param {string} value\n * @returns boolean\n */\n\nexport function isNumeric(value) {\n    return Boolean(value?.match(/^\\d+$/));\n}\n\n/**\n * Parse the string to check if the value is true or false\n * If the string is empty, 0, False or false it's considered as false\n * The rest is considered as true\n *\n * @param {string} str\n * @param {boolean} [trueIfEmpty=false]\n * @returns {boolean}\n */\nexport function exprToBoolean(str, trueIfEmpty = false) {\n    return str ? !/^false|0$/i.test(str) : trueIfEmpty;\n}\n\n/**\n * Generate a unique identifier (64 bits) in hexadecimal.\n *\n * @returns {string}\n */\nexport function uuid() {\n    const array = new Uint8Array(8);\n    window.crypto.getRandomValues(array);\n    // Uint8Array to hex\n    return [...array].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Generate a hash, also known as a 'digest', for the given string.\n * This algorithm is based on the Java hashString method\n * (see: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#hashCode()).\n * Please note that this hash function is non-cryptographic and does not exhibit collision resistance.\n *\n * If a cryptographic hash function is required, the digest() function of the SubtleCrypto\n * interface makes various hash functions available:\n * https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest\n *\n * @param {string} str\n * @returns {string}\n */\nexport function hashCode(...strings) {\n    const str = strings.join(\"\\x1C\");\n\n    let hash = 0;\n    for (let i = 0; i < str.length; i++) {\n        hash = (hash << 5) - hash + str.charCodeAt(i);\n        hash |= 0;\n    }\n\n    // Convert the possibly negative number hash code into an 8 character\n    // hexadecimal string\n    return (hash + 16 ** 8).toString(16).slice(-8);\n}\n", "import { browser } from \"@web/core/browser/browser\";\nimport { onWillUnmount, useComponent } from \"@odoo/owl\";\n\n/**\n * Creates a batched version of a callback so that all calls to it in the same\n * time frame will only call the original callback once.\n * @param callback the callback to batch\n * @param synchronize this function decides the granularity of the batch (a microtick by default)\n * @returns a batched version of the original callback\n */\nexport function batched(callback, synchronize = () => Promise.resolve()) {\n    let scheduled = false;\n    return async (...args) => {\n        if (!scheduled) {\n            scheduled = true;\n            await synchronize();\n            scheduled = false;\n            callback(...args);\n        }\n    };\n}\n\n/**\n * Creates and returns a new debounced version of the passed function (func)\n * which will postpone its execution until after 'delay' milliseconds\n * have elapsed since the last time it was invoked. The debounced function\n * will return a Promise that will be resolved when the function (func)\n * has been fully executed.\n *\n * If both `options.trailing` and `options.leading` are true, the function\n * will only be invoked at the trailing edge if the debounced function was\n * called at least once more during the wait time.\n *\n * @template {Function} T the return type of the original function\n * @param {T} func the function to debounce\n * @param {number | \"animationFrame\"} delay how long should elapse before the function\n *      is called. If 'animationFrame' is given instead of a number, 'requestAnimationFrame'\n *      will be used instead of 'setTimeout'.\n * @param {boolean} [options] if true, equivalent to exclusive leading. If false, equivalent to exclusive trailing.\n * @param {object} [options]\n * @param {boolean} [options.leading=false] whether the function should be invoked at the leading edge of the timeout\n * @param {boolean} [options.trailing=true] whether the function should be invoked at the trailing edge of the timeout\n * @returns {T & { cancel: () => void }} the debounced function\n */\nexport function debounce(func, delay, options) {\n    let handle;\n    const funcName = func.name ? func.name + \" (debounce)\" : \"debounce\";\n    const useAnimationFrame = delay === \"animationFrame\";\n    const setFnName = useAnimationFrame ? \"requestAnimationFrame\" : \"setTimeout\";\n    const clearFnName = useAnimationFrame ? \"cancelAnimationFrame\" : \"clearTimeout\";\n    let lastArgs;\n    let leading = false;\n    let trailing = true;\n    if (typeof options === \"boolean\") {\n        leading = options;\n        trailing = !options;\n    } else if (options) {\n        leading = options.leading ?? leading;\n        trailing = options.trailing ?? trailing;\n    }\n\n    return Object.assign(\n        {\n            /** @type {any} */\n            [funcName](...args) {\n                return new Promise((resolve) => {\n                    if (leading && !handle) {\n                        Promise.resolve(func.apply(this, args)).then(resolve);\n                    } else {\n                        lastArgs = args;\n                    }\n                    browser[clearFnName](handle);\n                    handle = browser[setFnName](() => {\n                        handle = null;\n                        if (trailing && lastArgs) {\n                            Promise.resolve(func.apply(this, lastArgs)).then(resolve);\n                            lastArgs = null;\n                        }\n                    }, delay);\n                });\n            },\n        }[funcName],\n        {\n            cancel(execNow = false) {\n                browser[clearFnName](handle);\n                if (execNow && lastArgs) {\n                    func.apply(this, lastArgs);\n                }\n            },\n        }\n    );\n}\n\n/**\n * Function that calls recursively a request to an animation frame.\n * Useful to call a function repetitively, until asked to stop, that needs constant rerendering.\n * The provided callback gets as argument the time the last frame took.\n * @param {(deltaTime: number) => void} callback\n * @returns {() => void} stop function\n */\nexport function setRecurringAnimationFrame(callback) {\n    const handler = (timestamp) => {\n        callback(timestamp - lastTimestamp);\n        lastTimestamp = timestamp;\n        handle = browser.requestAnimationFrame(handler);\n    };\n\n    const stop = () => {\n        browser.cancelAnimationFrame(handle);\n    };\n\n    let lastTimestamp = browser.performance.now();\n    let handle = browser.requestAnimationFrame(handler);\n\n    return stop;\n}\n\n/**\n * Creates a version of the function where only the last call between two\n * animation frames is executed before the browser's next repaint. This\n * effectively throttles the function to the display's refresh rate.\n * Note that the throttled function can be any callback. It is not\n * specifically an event handler, no assumption is made about its\n * signature.\n * NB: The first call is always called immediately (leading edge).\n *\n * @template {Function} T\n * @param {T} func the function to throttle\n * @returns {T & { cancel: () => void }} the throttled function\n */\nexport function throttleForAnimation(func) {\n    let handle = null;\n    const calls = new Set();\n    const funcName = func.name ? `${func.name} (throttleForAnimation)` : \"throttleForAnimation\";\n    const pending = () => {\n        if (calls.size) {\n            handle = browser.requestAnimationFrame(pending);\n            const { args, resolve } = [...calls].pop();\n            calls.clear();\n            Promise.resolve(func.apply(this, args)).then(resolve);\n        } else {\n            handle = null;\n        }\n    };\n    return Object.assign(\n        {\n            /** @type {any} */\n            [funcName](...args) {\n                return new Promise((resolve) => {\n                    const isNew = handle === null;\n                    if (isNew) {\n                        handle = browser.requestAnimationFrame(pending);\n                        Promise.resolve(func.apply(this, args)).then(resolve);\n                    } else {\n                        calls.add({ args, resolve });\n                    }\n                });\n            },\n        }[funcName],\n        {\n            cancel() {\n                browser.cancelAnimationFrame(handle);\n                calls.clear();\n                handle = null;\n            },\n        }\n    );\n}\n\n// ----------------------------------- HOOKS -----------------------------------\n\n/**\n * Hook that returns a debounced version of the given function, and cancels\n * the potential pending execution on willUnmount.\n * @see debounce\n * @template {Function} T\n * @param {T} callback\n * @param {number | \"animationFrame\"} delay\n * @param {Object} [options]\n * @param {string} [options.execBeforeUnmount=false] executes the callback if the debounced function\n *      has been called and not resolved before destroying the component.\n * @param {boolean} [options.immediate=false] whether the function should be called on\n *      the leading edge of the timeout.\n * @param {boolean} [options.trailing=!options.immediate] whether the function should be called on\n *      the trailing edge of the timeout.\n * @returns {T & { cancel: () => void }}\n */\nexport function useDebounced(\n    callback,\n    delay,\n    { execBeforeUnmount = false, immediate = false, trailing = !immediate } = {}\n) {\n    const component = useComponent();\n    const debounced = debounce(callback.bind(component), delay, { leading: immediate, trailing });\n    onWillUnmount(() => debounced.cancel(execBeforeUnmount));\n    return debounced;\n}\n\n/**\n * Hook that returns a throttled for animation version of the given function,\n * and cancels the potential pending execution on willUnmount.\n * @see throttleForAnimation\n * @template {Function} T\n * @param {T} func the function to throttle\n * @returns {T & { cancel: () => void }} the throttled function\n */\nexport function useThrottleForAnimation(func) {\n    const component = useComponent();\n    const throttledForAnimation = throttleForAnimation(func.bind(component));\n    onWillUnmount(() => throttledForAnimation.cancel());\n    return throttledForAnimation;\n}\n", "import { session } from \"@web/session\";\nimport { browser } from \"../browser/browser\";\nimport { shallowEqual } from \"@web/core/utils/objects\";\nconst { DateTime } = luxon;\n\nexport class RedirectionError extends Error {}\n\n/**\n * Transforms a key value mapping to a string formatted as url hash, e.g.\n * {a: \"x\", b: 2} -> \"a=x&b=2\"\n *\n * @param {Object} obj\n * @returns {string}\n */\nexport function objectToUrlEncodedString(obj) {\n    return Object.entries(obj)\n        .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v || \"\")}`)\n        .join(\"&\");\n}\n\n/**\n * Gets the origin url of the page, or cleans a given one\n *\n * @param {string} [origin]: a given origin url\n * @return {string} a cleaned origin url\n */\nexport function getOrigin(origin) {\n    if (origin) {\n        // remove trailing slashes\n        origin = origin.replace(/\\/+$/, \"\");\n    } else {\n        const { host, protocol } = browser.location;\n        origin = `${protocol}//${host}`;\n    }\n    return origin;\n}\n\n/**\n * @param {string} route: the relative route, or absolute in the case of cors urls\n * @param {object} [queryParams]: parameters to be appended as the url's queryString\n * @param {object} [options]\n * @param {string} [options.origin]: a precomputed origin\n */\nexport function url(route, queryParams, options = {}) {\n    const origin = getOrigin(options.origin ?? session.origin);\n    if (!route) {\n        return origin;\n    }\n\n    let queryString = objectToUrlEncodedString(queryParams || {});\n    queryString = queryString.length > 0 ? `?${queryString}` : queryString;\n\n    // Compare the wanted url against the current origin\n    let prefix = [\"http://\", \"https://\", \"//\"].some(\n        (el) => route.length >= el.length && route.slice(0, el.length) === el\n    );\n    prefix = prefix ? \"\" : origin;\n    return `${prefix}${route}${queryString}`;\n}\n\n/**\n * @param {string} model\n * @param {number} id\n * @param {string} field\n * @param {Object} [options]\n * @param {string} [options.crop]\n * @param {string} [options.filename]\n * @param {number} [options.height]\n * @param {string|import('luxon').DateTime} [options.unique]\n * @param {number} [options.width]\n */\nexport function imageUrl(\n    model,\n    id,\n    field,\n    { access_token, crop, filename, height, unique, width } = {}\n) {\n    let route = `/web/image/${model}/${id}/${field}`;\n    if (width && height) {\n        route = `${route}/${width}x${height}`;\n    }\n    if (filename) {\n        route = `${route}/${filename}`;\n    }\n    const urlParams = {};\n    if (access_token) {\n        Object.assign(urlParams, { access_token });\n    }\n    if (crop) {\n        Object.assign(urlParams, { crop });\n    }\n    if (unique) {\n        if (unique instanceof DateTime) {\n            urlParams.unique = unique.ts;\n        } else {\n            const dateTimeFromUnique = DateTime.fromSQL(unique);\n            if (dateTimeFromUnique.isValid) {\n                urlParams.unique = dateTimeFromUnique.ts;\n            } else if (typeof unique === \"string\" && unique.length > 0) {\n                urlParams.unique = unique;\n            }\n        }\n    }\n    return url(route, urlParams);\n}\n\n/**\n * Gets dataURL (base64 data) from the given file or blob.\n * Technically wraps FileReader.readAsDataURL in Promise.\n *\n * @param {Blob | File} file\n * @returns {Promise} resolved with the dataURL, or rejected if the file is\n *  empty or if an error occurs.\n */\nexport function getDataURLFromFile(file) {\n    if (!file) {\n        return Promise.reject();\n    }\n    return new Promise((resolve, reject) => {\n        const reader = new FileReader();\n        reader.addEventListener(\"load\", () => {\n            // Handle Chrome bug that creates invalid data URLs for empty files\n            if (reader.result === \"data:\") {\n                resolve(`data:${file.type};base64,`);\n            } else {\n                resolve(reader.result);\n            }\n        });\n        reader.addEventListener(\"abort\", reject);\n        reader.addEventListener(\"error\", reject);\n        reader.readAsDataURL(file);\n    });\n}\n\n/**\n * Safely redirects to the given url within the same origin.\n *\n * @param {string} url\n * @throws {RedirectionError} if the given url has a different origin\n */\nexport function redirect(url) {\n    const { origin, pathname } = browser.location;\n    const _url = new URL(url, `${origin}${pathname}`);\n    if (_url.origin !== origin) {\n        throw new RedirectionError(\"Can't redirect to another origin\");\n    }\n    browser.location.assign(_url.href);\n}\n\n/**\n * This function compares two URLs. It doesn't care about the order of the search parameters.\n *\n * @param {string} _url1\n * @param {string} _url2\n * @returns {boolean} true if the urls are identical, false otherwise\n */\nexport function compareUrls(_url1, _url2) {\n    const url1 = new URL(_url1);\n    const url2 = new URL(_url2);\n    return (\n        url1.origin === url2.origin &&\n        url1.pathname === url2.pathname &&\n        shallowEqual(\n            Object.fromEntries(url1.searchParams),\n            Object.fromEntries(url2.searchParams)\n        ) &&\n        url1.hash === url2.hash\n    );\n}\n", "import { isIterable } from \"./arrays\";\n\n/**\n * XML document to create new elements from. The fact that this is a \"text/xml\"\n * document ensures that tagNames and attribute names are case sensitive.\n */\nconst serializer = new XMLSerializer();\nconst parser = new DOMParser();\nconst xmlDocument = parser.parseFromString(\"<templates/>\", \"text/xml\");\n\nfunction hasParsingError(parsedDocument) {\n    return parsedDocument.getElementsByTagName(\"parsererror\").length > 0;\n}\n\n/**\n * @param {string} str\n * @returns {Element}\n */\nexport function parseXML(str) {\n    const xml = parser.parseFromString(str, \"text/xml\");\n    if (hasParsingError(xml)) {\n        throw new Error(\n            `An error occured while parsing ${str}: ${xml.getElementsByTagName(\"parsererror\")}`\n        );\n    }\n    return xml.documentElement;\n}\n\n/**\n * @param {Element} xml\n * @returns {string}\n */\nexport function serializeXML(xml) {\n    return serializer.serializeToString(xml);\n}\n\n/**\n * @param {Element | string} xml\n * @param {(el: Element, visitChildren: () => any) => any} callback\n */\nexport function visitXML(xml, callback) {\n    const visit = (el) => {\n        if (el) {\n            let didVisitChildren = false;\n            const visitChildren = () => {\n                for (const child of el.children) {\n                    visit(child);\n                }\n                didVisitChildren = true;\n            };\n            const shouldVisitChildren = callback(el, visitChildren);\n            if (shouldVisitChildren !== false && !didVisitChildren) {\n                visitChildren();\n            }\n        }\n    };\n    const xmlDoc = typeof xml === \"string\" ? parseXML(xml) : xml;\n    visit(xmlDoc);\n}\n\n/**\n * @param {Element} parent\n * @param {Node | Node[] | void} node\n */\nexport function append(parent, node) {\n    const nodes = Array.isArray(node) ? node : [node];\n    parent.append(...nodes.filter(Boolean));\n    return parent;\n}\n\n/**\n * Combines the existing value of a node attribute with new given parts. The glue\n * is the string used to join the parts.\n *\n * @param {Element} el\n * @param {string} attr\n * @param {string | string[]} parts\n * @param {string} [glue=\" \"]\n */\nexport function combineAttributes(el, attr, parts, glue = \" \") {\n    const allValues = [];\n    if (el.hasAttribute(attr)) {\n        allValues.push(el.getAttribute(attr));\n    }\n    parts = Array.isArray(parts) ? parts : [parts];\n    parts = parts.filter((part) => !!part);\n    allValues.push(...parts);\n    el.setAttribute(attr, allValues.join(glue));\n}\n\n/**\n * XML equivalent of `document.createElement`.\n *\n * @param {string} tagName\n * @param {...(Iterable<Element> | Record<string, string>)} args\n * @returns {Element}\n */\nexport function createElement(tagName, ...args) {\n    const el = xmlDocument.createElement(tagName);\n    for (const arg of args) {\n        if (!arg) {\n            continue;\n        }\n        if (isIterable(arg)) {\n            // Children list\n            el.append(...arg);\n        } else if (typeof arg === \"object\") {\n            // Attributes\n            for (const name in arg) {\n                el.setAttribute(name, arg[name]);\n            }\n        }\n    }\n    return el;\n}\n\n/**\n * XML equivalent of `document.createTextNode`.\n *\n * @param {string} data\n * @returns {Text}\n */\nexport function createTextNode(data) {\n    return xmlDocument.createTextNode(data);\n}\n\n/**\n * Removes the given attributes on the given element and returns them as a dictionnary.\n * @param {Element} el\n * @param {string[]} attributes\n * @returns {Record<string, string>}\n */\nexport function extractAttributes(el, attributes) {\n    const attrs = Object.create(null);\n    for (const attr of attributes) {\n        attrs[attr] = el.getAttribute(attr) || \"\";\n        el.removeAttribute(attr);\n    }\n    return attrs;\n}\n\n/**\n * @param {Node} [node]\n * @param {boolean} [lower=false]\n * @returns {string}\n */\nexport function getTag(node, lower = false) {\n    const tag = (node && node.nodeName) || \"\";\n    return lower ? tag.toLowerCase() : tag;\n}\n\n/**\n * @param {Node} node\n * @param {Object} attributes\n */\nexport function setAttributes(node, attributes) {\n    for (const [name, value] of Object.entries(attributes)) {\n        node.setAttribute(name, value);\n    }\n}\n", "import { useComponent, useEffect, useExternalListener } from \"@odoo/owl\";\nimport { pick, shallowEqual } from \"@web/core/utils/objects\";\nimport { useThrottleForAnimation } from \"@web/core/utils/timing\";\n\n/**\n * @template T\n * @typedef VirtualGridParams\n * @property {ReturnType<typeof import(\"@odoo/owl\").useRef>} scrollableRef\n *  a ref to the scrollable element\n * @property {ScrollPosition} [initialScroll={ left: 0, top: 0 }]\n *  the initial scroll position of the scrollable element\n * @property {(changed: Partial<VirtualGridIndexes>) => void} [onChange=() => this.render()]\n *  a callback called when the visible items change, i.e. when on scroll or resize.\n *  the default implementation is to re-render the component.\n * @property {number} [bufferCoef=1]\n *  the coefficient to calculate the buffer size around the visible area.\n *  The buffer size is equal to bufferCoef * windowSize.\n *  The default value is 1: it means that the buffer size takes one more window size on each side.\n *  So the whole area that will be rendered is 3 times the window size.\n *  If you use each direction, it could be up to 9 times the window size (3x3).\n *  Consider lowering this value if you have a costful rendering.\n *  A value of 0 means no buffer.\n */\n\n/**\n * @typedef VirtualGridIndexes\n * @property {[number, number] | undefined} columnsIndexes\n * @property {[number, number] | undefined} rowsIndexes\n */\n\n/**\n * @typedef VirtualGridSetters\n * @property {(widths: number[]) => void} setColumnsWidths\n *  Use it to set the width of each column.\n *  Indexes should match the indexes of the columns.\n * @property {(heights: number[]) => void} setRowsHeights\n *  Use it to set the height of each row.\n *  Indexes should match the indexes of the rows.\n */\n\n/**\n * @typedef ScrollPosition\n * @property {number} left\n * @property {number} top\n */\n\nconst BUFFER_COEFFICIENT = 1;\n\n/**\n * @typedef GetIndexesParams\n * @property {number[]} sizes contains the sizes of the items. Each size is the sum of the sizes of the previous items and the size of the current item.\n * @property {number} start it is the start position of the visible area, here it is the scroll position.\n * @property {number} span it is the size of the visible area, here it is the window size.\n * @property {number} [prevStartIndex] the previous start index, it is used to optimize the calculation.\n * @property {number} [bufferCoef=BUFFER_COEFFICIENT] the coefficient to calculate the buffer size.\n */\n\n/**\n * This function calculates the indexes of the visible items in a virtual list.\n *\n * @param {GetIndexesParams} param0\n * @returns {[number, number] | undefined} the indexes of the visible items with a surrounding buffer of totalSize on each side.\n */\nfunction getIndexes({ sizes, start, span, prevStartIndex, bufferCoef = BUFFER_COEFFICIENT }) {\n    if (!sizes || !sizes.length) {\n        return [];\n    }\n    if (sizes.at(-1) < span) {\n        // all items could be displayed\n        return [0, sizes.length - 1];\n    }\n    const bufferSize = Math.round(span * bufferCoef);\n    const bufferStart = start - bufferSize;\n    const bufferEnd = start + span + bufferSize;\n\n    let startIndex = prevStartIndex ?? 0;\n    // we search the first index such that sizes[index] > bufferStart\n    while (startIndex > 0 && sizes[startIndex] > bufferStart) {\n        startIndex--;\n    }\n    while (startIndex < sizes.length - 1 && sizes[startIndex] <= bufferStart) {\n        startIndex++;\n    }\n\n    let endIndex = startIndex;\n    // we search the last index such that (sizes[index - 1] ?? 0) < bufferEnd\n    while (endIndex < sizes.length - 1 && (sizes[endIndex - 1] ?? 0) < bufferEnd) {\n        endIndex++;\n    }\n    while (endIndex > startIndex && (sizes[endIndex - 1] ?? 0) >= bufferEnd) {\n        endIndex--;\n    }\n    return [startIndex, endIndex];\n}\n\n/**\n * Calculates the displayed items in a virtual grid.\n *\n * Requirements:\n *  - the scrollable area has a fixed height and width.\n *  - the items are rendered with a proper offset inside the scrollable area.\n *    This can be achieved e.g. with a css grid or an absolute positioning.\n *\n * @template T\n * @param {VirtualGridParams<T>} params\n * @returns {VirtualGridIndexes & VirtualGridSetters}\n */\nexport function useVirtualGrid({ scrollableRef, initialScroll, onChange, bufferCoef }) {\n    const comp = useComponent();\n    onChange ||= () => comp.render();\n\n    const current = { scroll: { left: 0, top: 0, ...initialScroll } };\n    const computeColumnsIndexes = () => {\n        return getIndexes({\n            sizes: current.summedColumnsWidths,\n            start: Math.abs(current.scroll.left),\n            span: window.innerWidth,\n            prevStartIndex: current.columnsIndexes?.[0],\n            bufferCoef,\n        });\n    };\n    const computeRowsIndexes = () => {\n        return getIndexes({\n            sizes: current.summedRowsHeights,\n            start: current.scroll.top,\n            span: window.innerHeight,\n            prevStartIndex: current.rowsIndexes?.[0],\n            bufferCoef,\n        });\n    };\n    const throttledCompute = useThrottleForAnimation(() => {\n        const changed = [];\n        const columnsVisibleIndexes = computeColumnsIndexes();\n        if (!shallowEqual(columnsVisibleIndexes, current.columnsIndexes)) {\n            current.columnsIndexes = columnsVisibleIndexes;\n            changed.push(\"columnsIndexes\");\n        }\n        const rowsVisibleIndexes = computeRowsIndexes();\n        if (!shallowEqual(rowsVisibleIndexes, current.rowsIndexes)) {\n            current.rowsIndexes = rowsVisibleIndexes;\n            changed.push(\"rowsIndexes\");\n        }\n        if (changed.length) {\n            onChange(pick(current, ...changed));\n        }\n    });\n    const scrollListener = (/** @type {Event & { target: Element }} */ ev) => {\n        current.scroll.left = ev.target.scrollLeft;\n        current.scroll.top = ev.target.scrollTop;\n        throttledCompute();\n    };\n    useEffect(\n        (el) => {\n            el?.addEventListener(\"scroll\", scrollListener);\n            return () => el?.removeEventListener(\"scroll\", scrollListener);\n        },\n        () => [scrollableRef.el]\n    );\n    useExternalListener(window, \"resize\", () => throttledCompute());\n    return {\n        get columnsIndexes() {\n            return current.columnsIndexes;\n        },\n        get rowsIndexes() {\n            return current.rowsIndexes;\n        },\n        setColumnsWidths(widths) {\n            let acc = 0;\n            current.summedColumnsWidths = widths.map((w) => (acc += w));\n            delete current.columnsIndexes;\n            current.columnsIndexes = computeColumnsIndexes();\n        },\n        setRowsHeights(heights) {\n            let acc = 0;\n            current.summedRowsHeights = heights.map((h) => (acc += h));\n            delete current.rowsIndexes;\n            current.rowsIndexes = computeRowsIndexes();\n        },\n    };\n}\n", "import { isMacOS } from \"@web/core/browser/feature_detection\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { capitalize } from \"@web/core/utils/strings\";\nimport { getVisibleElements } from \"@web/core/utils/ui\";\nimport { DefaultCommandItem } from \"./command_palette\";\n\nimport { Component } from \"@odoo/owl\";\n\nconst commandSetupRegistry = registry.category(\"command_setup\");\ncommandSetupRegistry.add(\"default\", {\n    emptyMessage: _t(\"No command found\"),\n    placeholder: _t(\"Search for a command...\"),\n});\n\nexport class HotkeyCommandItem extends Component {\n    static template = \"web.HotkeyCommandItem\";\n    static props = [\"hotkey\", \"hotkeyOptions?\", \"name?\", \"searchValue?\", \"executeCommand\", \"slots\"];\n    setup() {\n        useHotkey(this.props.hotkey, this.props.executeCommand);\n    }\n\n    getKeysToPress(command) {\n        const { hotkey } = command;\n        let result = hotkey.split(\"+\");\n        if (isMacOS()) {\n            result = result\n                .map((x) => x.replace(\"control\", \"command\"))\n                .map((x) => x.replace(\"alt\", \"control\"));\n        }\n        return result.map((key) => key.toUpperCase());\n    }\n}\n\nconst commandCategoryRegistry = registry.category(\"command_categories\");\nconst commandProviderRegistry = registry.category(\"command_provider\");\ncommandProviderRegistry.add(\"command\", {\n    provide: (env, options = {}) => {\n        const commands = env.services.command\n            .getCommands(options.activeElement)\n            .map((cmd) => {\n                cmd.category = commandCategoryRegistry.contains(cmd.category)\n                    ? cmd.category\n                    : \"default\";\n                return cmd;\n            })\n            .filter((command) => command.isAvailable === undefined || command.isAvailable());\n        // Filter out same category dupplicate commands\n        const uniqueCommands = commands.filter((obj, index) => {\n            return (\n                index ===\n                commands.findIndex((o) => obj.name === o.name && obj.category === o.category)\n            );\n        });\n        return uniqueCommands.map((command) => ({\n            Component: command.hotkey ? HotkeyCommandItem : DefaultCommandItem,\n            action: command.action,\n            category: command.category,\n            name: command.name,\n            props: {\n                hotkey: command.hotkey,\n                hotkeyOptions: command.hotkeyOptions,\n            },\n        }));\n    },\n});\n\ncommandProviderRegistry.add(\"data-hotkeys\", {\n    provide: (env, options = {}) => {\n        const commands = [];\n        const overlayModifier = registry.category(\"services\").get(\"hotkey\").overlayModifier;\n        // Also retrieve all hotkeyables elements\n        for (const el of getVisibleElements(\n            options.activeElement,\n            \"[data-hotkey]:not(:disabled)\"\n        )) {\n            const closest = el.closest(\"[data-command-category]\");\n            const category = closest ? closest.dataset.commandCategory : \"default\";\n            if (category === \"disabled\") {\n                continue;\n            }\n\n            const description =\n                el.title ||\n                el.dataset.bsOriginalTitle || // LEGACY: bootstrap moves title to data-bs-original-title\n                el.dataset.tooltip ||\n                el.placeholder ||\n                (el.innerText &&\n                    `${el.innerText.slice(0, 50)}${el.innerText.length > 50 ? \"...\" : \"\"}`) ||\n                _t(\"no description provided\");\n\n            commands.push({\n                Component: HotkeyCommandItem,\n                action: () => {\n                    // AAB: not sure it is enough, we might need to trigger all events that occur when you actually click\n                    el.focus();\n                    el.click();\n                },\n                category,\n                name: capitalize(description.trim().toLowerCase()),\n                props: {\n                    hotkey: `${overlayModifier}+${el.dataset.hotkey}`,\n                },\n            });\n        }\n        return commands;\n    },\n});\n", "import { Dialog } from \"@web/core/dialog/dialog\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { KeepLast, Race } from \"@web/core/utils/concurrency\";\nimport { useAutofocus, useService } from \"@web/core/utils/hooks\";\nimport { scrollTo } from \"@web/core/utils/scrolling\";\nimport { fuzzyLookup } from \"@web/core/utils/search\";\nimport { debounce } from \"@web/core/utils/timing\";\nimport { isMacOS, isMobileOS } from \"@web/core/browser/feature_detection\";\nimport { highlightText } from \"@web/core/utils/html\";\n\nimport {\n    Component,\n    onWillStart,\n    onWillDestroy,\n    EventBus,\n    useRef,\n    useState,\n    markRaw,\n    useExternalListener,\n} from \"@odoo/owl\";\n\nconst DEFAULT_PLACEHOLDER = _t(\"Search...\");\nconst DEFAULT_EMPTY_MESSAGE = _t(\"No result found\");\nconst FUZZY_NAMESPACES = [\"default\"];\n\n/**\n * @typedef {import(\"./command_service\").Command} Command\n */\n\n/**\n * @typedef {Command & {\n *  Component?: Component;\n *  props?: object;\n * }} CommandItem\n */\n\n/**\n * @typedef {{\n *  namespace?: string;\n *  provide: ()=>CommandItem[];\n * }} Provider\n */\n\n/**\n * @typedef {{\n *  categories: string[];\n *  debounceDelay: number;\n *  emptyMessage: string;\n *  placeholder: string;\n * }} NamespaceConfig\n */\n\n/**\n * @typedef {{\n *  configByNamespace?: {[namespace: string]: NamespaceConfig};\n *  FooterComponent?: Component;\n *  providers: Provider[];\n *  searchValue?: string;\n * }} CommandPaletteConfig\n */\n\n/**\n * Util used to filter commands that are within category.\n * Note: for the default category, also get all commands having invalid category.\n *\n * @param {string} categoryName the category key\n * @param {string[]} categories\n * @returns an array filter predicate\n */\nfunction commandsWithinCategory(categoryName, categories) {\n    return (cmd) => {\n        const inCurrentCategory = categoryName === cmd.category;\n        const fallbackCategory = categoryName === \"default\" && !categories.includes(cmd.category);\n        return inCurrentCategory || fallbackCategory;\n    };\n}\n\nexport class DefaultCommandItem extends Component {\n    static template = \"web.DefaultCommandItem\";\n    static props = {\n        slots: { type: Object, optional: true },\n        // Props send by the command palette:\n        hotkey: { type: String, optional: true },\n        hotkeyOptions: { type: String, optional: true },\n        name: { type: String, optional: true },\n        searchValue: { type: String, optional: true },\n        executeCommand: { type: Function, optional: true },\n    };\n}\n\nexport class CommandPalette extends Component {\n    static template = \"web.CommandPalette\";\n    static components = { Dialog };\n    static lastSessionId = 0;\n    static props = {\n        bus: { type: EventBus, optional: true },\n        close: Function,\n        config: Object,\n        closeMe: { type: Function, optional: true },\n    };\n\n    setup() {\n        if (this.props.bus) {\n            const setConfig = ({ detail }) => this.setCommandPaletteConfig(detail);\n            this.props.bus.addEventListener(`SET-CONFIG`, setConfig);\n            onWillDestroy(() => this.props.bus.removeEventListener(`SET-CONFIG`, setConfig));\n        }\n\n        this.keyId = 1;\n        this.race = new Race();\n        this.keepLast = new KeepLast();\n        this._sessionId = CommandPalette.lastSessionId++;\n        this.DefaultCommandItem = DefaultCommandItem;\n        this.activeElement = useService(\"ui\").activeElement;\n        this.inputRef = useAutofocus();\n\n        useHotkey(\"Enter\", () => this.executeSelectedCommand(), { bypassEditableProtection: true });\n        useHotkey(\"Control+Enter\", () => this.executeSelectedCommand(true), {\n            bypassEditableProtection: true,\n        });\n        useHotkey(\"ArrowUp\", () => this.selectCommandAndScrollTo(\"PREV\"), {\n            bypassEditableProtection: true,\n            allowRepeat: true,\n        });\n        useHotkey(\"ArrowDown\", () => this.selectCommandAndScrollTo(\"NEXT\"), {\n            bypassEditableProtection: true,\n            allowRepeat: true,\n        });\n        useExternalListener(window, \"mousedown\", this.onWindowMouseDown);\n\n        /**\n         * @type {{ commands: CommandItem[],\n         *          emptyMessage: string,\n         *          FooterComponent: Component,\n         *          namespace: string,\n         *          placeholder: string,\n         *          searchValue: string,\n         *          selectedCommand: CommandItem }}\n         */\n        this.state = useState({});\n\n        this.root = useRef(\"root\");\n        this.listboxRef = useRef(\"listbox\");\n\n        onWillStart(() => this.setCommandPaletteConfig(this.props.config));\n    }\n\n    get commandsByCategory() {\n        const categories = [];\n        for (const category of this.categoryKeys) {\n            const commands = this.state.commands.filter(\n                commandsWithinCategory(category, this.categoryKeys)\n            );\n            if (commands.length) {\n                categories.push({\n                    commands,\n                    name: this.categoryNames[category],\n                    keyId: category,\n                });\n            }\n        }\n        return categories;\n    }\n\n    /**\n     * Apply the new config to the command pallet\n     * @param {CommandPaletteConfig} config\n     */\n    async setCommandPaletteConfig(config) {\n        this.configByNamespace = config.configByNamespace || {};\n        this.state.FooterComponent = config.FooterComponent;\n\n        this.providersByNamespace = { default: [] };\n        for (const provider of config.providers) {\n            const namespace = provider.namespace || \"default\";\n            if (namespace in this.providersByNamespace) {\n                this.providersByNamespace[namespace].push(provider);\n            } else {\n                this.providersByNamespace[namespace] = [provider];\n            }\n        }\n\n        const { namespace, searchValue } = this.processSearchValue(config.searchValue || \"\");\n        this.switchNamespace(namespace);\n        this.state.searchValue = searchValue;\n        await this.race.add(this.search(searchValue));\n    }\n\n    /**\n     * Modifies the commands to be displayed according to the namespace and the options.\n     * Selects the first command in the new list.\n     * @param {string} namespace\n     * @param {object} options\n     */\n    async setCommands(namespace, options = {}) {\n        this.categoryKeys = [\"default\"];\n        this.categoryNames = {};\n        const proms = this.providersByNamespace[namespace].map((provider) => {\n            const { provide } = provider;\n            const result = provide(this.env, options);\n            return result;\n        });\n        let commands = (await this.keepLast.add(Promise.all(proms))).flat();\n        const namespaceConfig = this.configByNamespace[namespace] || {};\n        if (options.searchValue && FUZZY_NAMESPACES.includes(namespace)) {\n            commands = fuzzyLookup(options.searchValue, commands, (c) => c.name);\n        } else {\n            // we have to sort the commands by category to avoid navigation issues with the arrows\n            if (namespaceConfig.categories) {\n                let commandsSorted = [];\n                this.categoryKeys = namespaceConfig.categories;\n                this.categoryNames = namespaceConfig.categoryNames || {};\n                if (!this.categoryKeys.includes(\"default\")) {\n                    this.categoryKeys.push(\"default\");\n                }\n                for (const category of this.categoryKeys) {\n                    commandsSorted = commandsSorted.concat(\n                        commands.filter(commandsWithinCategory(category, this.categoryKeys))\n                    );\n                }\n                commands = commandsSorted;\n            }\n        }\n\n        this.state.commands = markRaw(\n            commands.slice(0, 100).map((command) => ({\n                ...command,\n                keyId: this.keyId++,\n                text: highlightText(options.searchValue, command.name, \"fw-bolder text-primary\"),\n            }))\n        );\n        this.selectCommand(this.state.commands.length ? 0 : -1);\n        this.mouseSelectionActive = false;\n        this.state.emptyMessage = (\n            namespaceConfig.emptyMessage || DEFAULT_EMPTY_MESSAGE\n        ).toString();\n    }\n\n    selectCommand(index) {\n        if (index === -1 || index >= this.state.commands.length) {\n            this.state.selectedCommand = null;\n            return;\n        }\n        this.state.selectedCommand = markRaw(this.state.commands[index]);\n    }\n\n    selectCommandAndScrollTo(type) {\n        // In case the mouse is on the palette command, it avoids the selection\n        // of a command caused by a scroll.\n        this.mouseSelectionActive = false;\n        const index = this.state.commands.indexOf(this.state.selectedCommand);\n        if (index === -1) {\n            return;\n        }\n        let nextIndex;\n        if (type === \"NEXT\") {\n            nextIndex = index < this.state.commands.length - 1 ? index + 1 : 0;\n        } else if (type === \"PREV\") {\n            nextIndex = index > 0 ? index - 1 : this.state.commands.length - 1;\n        }\n        this.selectCommand(nextIndex);\n\n        const command = this.listboxRef.el.querySelector(`#o_command_${nextIndex}`);\n        scrollTo(command, { scrollable: this.listboxRef.el });\n    }\n\n    onCommandClicked(event, index) {\n        event.preventDefault(); // Prevent redirect for commands with href\n        this.selectCommand(index);\n        const ctrlKey = isMacOS() ? event.metaKey : event.ctrlKey;\n        this.executeSelectedCommand(ctrlKey);\n    }\n\n    /**\n     * Execute the action related to the order.\n     * If this action returns a config, then we will use it in the command palette,\n     * otherwise we close the command palette.\n     * @param {CommandItem} command\n     */\n    async executeCommand(command) {\n        const config = await command.action();\n        if (config) {\n            this.setCommandPaletteConfig(config);\n        } else {\n            this.props.close();\n        }\n    }\n\n    async executeSelectedCommand(ctrlKey) {\n        await this.searchValuePromise;\n        const selectedCommand = this.state.selectedCommand;\n        if (selectedCommand) {\n            if (!ctrlKey) {\n                this.executeCommand(selectedCommand);\n            } else if (selectedCommand.href) {\n                window.open(selectedCommand.href, \"_blank\");\n            }\n        }\n    }\n\n    onCommandMouseEnter(index) {\n        if (this.mouseSelectionActive) {\n            this.selectCommand(index);\n        } else {\n            this.mouseSelectionActive = true;\n        }\n    }\n\n    async search(searchValue) {\n        this.state.isLoading = true;\n        try {\n            await this.setCommands(this.state.namespace, {\n                searchValue,\n                activeElement: this.activeElement,\n                sessionId: this._sessionId,\n            });\n        } finally {\n            this.state.isLoading = false;\n        }\n        if (this.inputRef.el) {\n            this.inputRef.el.focus();\n        }\n    }\n\n    debounceSearch(value) {\n        const { namespace, searchValue } = this.processSearchValue(value);\n        if (namespace !== \"default\" && this.state.namespace !== namespace) {\n            this.switchNamespace(namespace);\n        }\n        this.state.searchValue = searchValue;\n        this.searchValuePromise = this.lastDebounceSearch(searchValue).catch(() => {\n            this.searchValuePromise = null;\n        });\n    }\n\n    onSearchInput(ev) {\n        this.debounceSearch(ev.target.value);\n    }\n\n    onKeyDown(ev) {\n        if (ev.key.toLowerCase() === \"backspace\" && !ev.target.value.length && !ev.repeat) {\n            this.switchNamespace(\"default\");\n            this.state.searchValue = \"\";\n            this.searchValuePromise = this.lastDebounceSearch(\"\").catch(() => {\n                this.searchValuePromise = null;\n            });\n        }\n    }\n\n    /**\n     * Close the palette on outside click.\n     */\n    onWindowMouseDown(ev) {\n        if (!this.root.el.contains(ev.target)) {\n            this.props.close();\n        }\n    }\n\n    switchNamespace(namespace) {\n        if (this.lastDebounceSearch) {\n            this.lastDebounceSearch.cancel();\n        }\n        const namespaceConfig = this.configByNamespace[namespace] || {};\n        this.lastDebounceSearch = debounce(\n            (value) => this.search(value),\n            namespaceConfig.debounceDelay || 0\n        );\n        this.state.namespace = namespace;\n        this.state.placeholder = namespaceConfig.placeholder || DEFAULT_PLACEHOLDER.toString();\n    }\n\n    processSearchValue(searchValue) {\n        let namespace = \"default\";\n        if (searchValue.length && this.providersByNamespace[searchValue[0]]) {\n            namespace = searchValue[0];\n            searchValue = searchValue.slice(1);\n        }\n        return { namespace, searchValue };\n    }\n\n    get isMacOS() {\n        return isMacOS();\n    }\n    get isMobileOS() {\n        return isMobileOS();\n    }\n}\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class CapsLockWarning extends Interaction {\n    static selector = \".o_caps_lock_warning\";\n    dynamicContent = {\n        \".o_caps_lock_warning_text\": {\n            \"t-att-class\": () => ({ \"d-none\": this.isWarningHidden }),\n        },\n        \".o_caps_lock_warning input[type='password']\": {\n            \"t-on-keydown\": this._onInputKeyDown,\n        },\n    };\n\n    setup() {\n        this.isWarningHidden = true;\n        this.renderAt(\"web.caps_lock_warning\");\n    }\n\n    /**\n     * Captures keydown events to detect the CAPS LOCK state and toggle the\n     * CAPS LOCK warning accordingly\n     *\n     * @private\n     * @param {KeyboardEvent} ev\n     */\n    _onInputKeyDown(ev) {\n        // FALSE when we first hit the CAPS-LOCK\n        // at this point, the CAPS-LOCK is yet to TURN ON.\n        const state = ev.getModifierState?.(\"CapsLock\");\n\n        // FALSE value REMOVES the `invisible` class while TRUE ADDS it.\n        this.isWarningHidden = ev.key === \"CapsLock\" ? state : !state;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"web.caps_lock_warning\", CapsLockWarning);\n", "/**\n * This is a mini framework designed to make it easy to describe the dynamic\n * content of a \"interaction\".\n */\n\nlet owl = null;\nlet Markup = null;\n\nexport const INITIAL_VALUE = Symbol(\"initial value\");\n// Return this from event handlers to skip updateContent.\nexport const SKIP_IMPLICIT_UPDATE = Symbol();\n\nexport class Colibri {\n    constructor(core, I, el) {\n        this.el = el;\n        this.isReady = false;\n        this.hasStarted = false;\n        this.isUpdating = false;\n        this.isDestroyed = false;\n        this.dynamicAttrs = [];\n        this.tOuts = [];\n        this.cleanups = [];\n        this.listeners = new Map();\n        this.dynamicNodes = new Map();\n        this.core = core;\n        this.interaction = new I(el, core.env, this);\n        this.setupInteraction();\n    }\n\n    setupInteraction() {\n        this.interaction.setup();\n    }\n\n    destroyInteraction() {\n        for (const cleanup of this.cleanups.reverse()) {\n            cleanup();\n        }\n        this.cleanups = [];\n        this.interaction.destroy();\n    }\n\n    startInteraction(content) {\n        if (content) {\n            this.processContent(content);\n            this.updateContent();\n        }\n        this.interaction.start();\n        this.hasStarted = true;\n    }\n\n    async start() {\n        await this.interaction.willStart();\n        if (this.isDestroyed) {\n            return;\n        }\n        this.isReady = true;\n        const content = this.interaction.dynamicContent;\n        this.startInteraction(content);\n    }\n\n    addListener(nodes, event, fn, options) {\n        if (typeof fn !== \"function\") {\n            throw new Error(`Invalid listener for event '${event}' (not a function)`);\n        }\n        if (!this.isReady) {\n            throw new Error(\n                \"this.addListener can only be called after the interaction is started. Maybe move the call in the start method.\"\n            );\n        }\n        const re = /^(?<event>.*)\\.(?<suffix>prevent|stop|capture|once|noUpdate|withTarget)$/;\n        let groups = re.exec(event)?.groups;\n        while (groups) {\n            fn = {\n                prevent:\n                    (f) =>\n                    (ev, ...args) => {\n                        ev.preventDefault();\n                        return f.call(this.interaction, ev, ...args);\n                    },\n                stop:\n                    (f) =>\n                    (ev, ...args) => {\n                        ev.stopPropagation();\n                        return f.call(this.interaction, ev, ...args);\n                    },\n                capture: (f) => {\n                    options ||= {};\n                    options.capture = true;\n                    return f;\n                },\n                once: (f) => {\n                    options ||= {};\n                    options.once = true;\n                    return f;\n                },\n                noUpdate:\n                    (f) =>\n                    (...args) => {\n                        f.call(this.interaction, ...args);\n                        return SKIP_IMPLICIT_UPDATE;\n                    },\n                withTarget:\n                    (f) =>\n                    (ev, ...args) => {\n                        const currentTarget = ev.currentTarget;\n                        return f.call(this.interaction, ev, currentTarget, ...args);\n                    },\n            }[groups.suffix](fn);\n            event = groups.event;\n            groups = re.exec(event)?.groups;\n        }\n        const handler = fn.isHandler\n            ? fn\n            : async (...args) => {\n                  if (SKIP_IMPLICIT_UPDATE !== (await fn.call(this.interaction, ...args))) {\n                      if (!this.isDestroyed) {\n                          this.updateContent();\n                      }\n                  }\n              };\n        handler.isHandler = true;\n        for (const node of nodes) {\n            node.addEventListener(event, handler, options);\n            this.cleanups.push(() => node.removeEventListener(event, handler, options));\n        }\n        return [event, handler, options];\n    }\n\n    refreshNodes() {\n        for (const sel of this.dynamicNodes.keys()) {\n            const nodes = this.getNodes(sel);\n            if (this.listeners.has(sel)) {\n                const newNodes = new Set(nodes);\n                const oldNodes = this.dynamicNodes.get(sel);\n                const events = this.listeners.get(sel);\n                const toRemove = new Set();\n                for (const node of oldNodes) {\n                    if (newNodes.has(node)) {\n                        newNodes.delete(node);\n                    } else {\n                        toRemove.add(node);\n                    }\n                }\n                for (const event of Object.keys(events)) {\n                    const [handler, options] = events[event];\n                    for (const node of toRemove) {\n                        node.removeEventListener(event, handler, options);\n                    }\n                    if (newNodes.size) {\n                        this.addListener(newNodes, event, handler, options);\n                    }\n                }\n            }\n            this.dynamicNodes.set(sel, nodes);\n        }\n    }\n\n    mapSelectorToListeners(sel, event, handler, options) {\n        if (this.listeners.has(sel)) {\n            this.listeners.get(sel)[event] = [handler, options];\n        } else {\n            this.listeners.set(sel, { [event]: [handler, options] });\n        }\n    }\n\n    mountComponent(node, C, props, position = \"beforeend\") {\n        const root = this.core.prepareRoot(node, C, props, position);\n        root.mount();\n        this.cleanups.push(() => root.destroy());\n        return root.destroy;\n    }\n\n    applyTOut(el, value, initialValue) {\n        if (value === INITIAL_VALUE) {\n            value = initialValue;\n        }\n        if (!Markup) {\n            if (owl) {\n                Markup = owl.markup(\"\").constructor;\n            }\n        }\n        if (Markup && value instanceof Markup) {\n            let nodes = el === this.interaction.el ? el.children : [el];\n            for (const node of nodes) {\n                this.core.env.services[\"public.interactions\"].stopInteractions(node);\n            }\n            el.innerHTML = value;\n            if (el === this.interaction.el) {\n                nodes = el.children;\n            }\n            for (const node of nodes) {\n                this.core.env.services[\"public.interactions\"].startInteractions(node);\n            }\n            this.refreshNodes();\n        } else {\n            el.textContent = value;\n        }\n    }\n\n    applyAttr(el, attr, value, initialValue) {\n        if (attr === \"class\") {\n            if (typeof value !== \"object\") {\n                throw new Error(\"t-att-class directive expects an object\");\n            }\n            for (const cl in value) {\n                let toApply = value[cl];\n                for (const c of cl.trim().split(\" \")) {\n                    if (toApply === INITIAL_VALUE) {\n                        toApply = initialValue[cl];\n                    }\n                    el.classList.toggle(c, toApply || false);\n                }\n            }\n        } else if (attr === \"style\") {\n            if (typeof value !== \"object\") {\n                throw new Error(\"t-att-style directive expects an object\");\n            }\n            for (const prop in value) {\n                let style = value[prop];\n                if (style === INITIAL_VALUE) {\n                    style = initialValue[prop];\n                }\n                if (style === undefined) {\n                    el.style.removeProperty(prop);\n                } else {\n                    style = String(style);\n                    if (style.endsWith(\" !important\")) {\n                        el.style.setProperty(\n                            prop,\n                            style.substring(0, style.length - 11),\n                            \"important\"\n                        );\n                    } else {\n                        el.style.setProperty(prop, style);\n                    }\n                }\n            }\n        } else {\n            if (value === INITIAL_VALUE) {\n                value = initialValue;\n            }\n            if ([false, undefined, null].includes(value)) {\n                el.removeAttribute(attr);\n            } else {\n                if (value === true) {\n                    value = attr;\n                }\n                el.setAttribute(attr, value);\n            }\n        }\n    }\n\n    getNodes(sel) {\n        const selectors = this.interaction.dynamicSelectors;\n        if (sel in selectors) {\n            const elems = selectors[sel]();\n            if (elems) {\n                if (elems.nodeName && [\"FORM\", \"SELECT\"].includes(elems.nodeName)) {\n                    return [elems];\n                }\n                return elems[Symbol.iterator] ? elems : [elems];\n            } else {\n                return [];\n            }\n        }\n        return this.interaction.el.querySelectorAll(sel);\n    }\n\n    processContent(content) {\n        for (const sel in content) {\n            if (sel.startsWith(\"t-\")) {\n                throw new Error(\n                    `Selector missing for key ${sel} in dynamicContent (interaction '${this.interaction.constructor.name}').`\n                );\n            }\n            let nodes;\n            if (this.dynamicNodes.has(sel)) {\n                nodes = this.dynamicNodes.get(sel);\n            } else {\n                nodes = this.getNodes(sel);\n                this.dynamicNodes.set(sel, nodes);\n            }\n            const descr = content[sel];\n            for (const directive in descr) {\n                const value = descr[directive];\n                if (directive.startsWith(\"t-on-\")) {\n                    const ev = directive.slice(5);\n                    const [event, handler, options] = this.addListener(nodes, ev, value);\n                    this.mapSelectorToListeners(sel, event, handler, options);\n                } else if (directive.startsWith(\"t-att-\")) {\n                    const attr = directive.slice(6);\n                    this.dynamicAttrs.push({\n                        sel,\n                        attr,\n                        definition: value,\n                        initialValues: null,\n                    });\n                } else if (directive === \"t-out\") {\n                    this.tOuts.push({ sel, definition: value, initialValue: null });\n                } else if (directive === \"t-component\") {\n                    const { Component } = odoo.loader.modules.get(\"@odoo/owl\");\n                    if (Object.prototype.isPrototypeOf.call(Component, value)) {\n                        for (const node of nodes) {\n                            this.mountComponent(node, value);\n                        }\n                    } else {\n                        for (const node of nodes) {\n                            this.mountComponent(node, ...value(node));\n                        }\n                    }\n                } else {\n                    const suffix = directive.startsWith(\"t-\") ? \"\" : \" (should start with t-)\";\n                    throw new Error(`Invalid directive: '${directive}'${suffix}`);\n                }\n            }\n        }\n    }\n\n    updateContent() {\n        if (this.isDestroyed || !this.isReady) {\n            throw new Error(\n                \"Cannot update content of an interaction that is not ready or is destroyed\"\n            );\n        }\n        if (this.isUpdating) {\n            throw new Error(\"Updatecontent should not be called while interaction is updating\");\n        }\n        this.isUpdating = true;\n        if (this.hasStarted) {\n            this.refreshNodes();\n        }\n        const errors = [];\n        const interaction = this.interaction;\n        for (const dynamicAttr of this.dynamicAttrs) {\n            let { sel, attr, definition, initialValues } = dynamicAttr;\n            const nodes = this.dynamicNodes.get(sel) || [];\n            if (!initialValues && nodes.length) {\n                initialValues = new Map();\n                dynamicAttr.initialValues = initialValues;\n            }\n            for (const node of nodes) {\n                try {\n                    const value = definition.call(interaction, node);\n                    if (!initialValues || !initialValues.has(node)) {\n                        let attrValue;\n                        switch (attr) {\n                            case \"class\":\n                                attrValue = {};\n                                for (const classNames of Object.keys(value)) {\n                                    attrValue[classNames] = node.classList.contains(classNames);\n                                }\n                                break;\n                            case \"style\":\n                                attrValue = {};\n                                for (const property of Object.keys(value)) {\n                                    const propertyValue = node.style.getPropertyValue(property);\n                                    const priority = node.style.getPropertyPriority(property);\n                                    attrValue[property] = propertyValue\n                                        ? propertyValue + (priority ? ` !${priority}` : \"\")\n                                        : undefined;\n                                }\n                                break;\n                            default:\n                                attrValue = node.getAttribute(attr);\n                        }\n                        initialValues.set(node, attrValue);\n                    }\n                    this.applyAttr(node, attr, value, dynamicAttr.initialValues.get(node));\n                } catch (e) {\n                    errors.push({ error: e, attribute: attr });\n                }\n            }\n        }\n        for (const tOut of this.tOuts) {\n            let { sel, definition, initialValue } = tOut;\n            const nodes = this.dynamicNodes.get(sel) || [];\n            if (!initialValue && nodes.length) {\n                initialValue = new Map();\n                tOut.initialValue = initialValue;\n            }\n            for (const node of nodes) {\n                if (!initialValue || !initialValue.has(node)) {\n                    if (!owl) {\n                        owl = odoo.loader.modules.get(\"@odoo/owl\");\n                    }\n                    const value = node.children.length\n                        ? owl.markup(node.innerHTML)\n                        : node.textContent;\n                    initialValue.set(node, value);\n                }\n                this.applyTOut(\n                    node,\n                    definition.call(interaction, node),\n                    tOut.initialValue.get(node)\n                );\n            }\n        }\n        this.isUpdating = false;\n        if (errors.length) {\n            const { attribute, error } = errors[0];\n            throw Error(\n                `An error occured while updating dynamic attribute '${attribute}' (in interaction '${this.interaction.constructor.name}')`,\n                { cause: error }\n            );\n        }\n    }\n\n    destroy() {\n        // restore t-att to their initial values\n        for (const dynAttrs of this.dynamicAttrs) {\n            const { sel, attr, initialValues } = dynAttrs;\n            if (!initialValues) {\n                continue;\n            }\n            for (const node of this.dynamicNodes.get(sel) || []) {\n                if (initialValues.has(node)) {\n                    const initialValue = initialValues.get(node);\n                    this.applyAttr(node, attr, initialValue);\n                }\n            }\n        }\n\n        for (const tOut of this.tOuts) {\n            const { sel, initialValue } = tOut;\n            if (!initialValue) {\n                continue;\n            }\n            for (const node of this.dynamicNodes.get(sel) || []) {\n                if (initialValue.has(node)) {\n                    const value = initialValue.get(node);\n                    this.applyTOut(node, value);\n                }\n            }\n        }\n\n        this.listeners.clear();\n        this.dynamicNodes.clear();\n        this.destroyInteraction();\n        this.core = null;\n        this.isDestroyed = true;\n        this.isReady = false;\n    }\n\n    /**\n     * Patchable mechanism to handle context-specific protection of a specific\n     * chunk of synchronous code after returning from an asynchronous one.\n     * This should typically be used around code that follows an\n     * await waitFor(...).\n     */\n    protectSyncAfterAsync(interaction, name, fn) {\n        return fn.bind(interaction);\n    }\n}\n", "import {\n    deserializeDate,\n    deserializeDateTime,\n    parseDate,\n    parseDateTime,\n} from \"@web/core/l10n/dates\";\nimport { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\n\nexport class DatetimePicker extends Interaction {\n    static selector = \"[data-widget='datetime-picker']\";\n\n    setup() {\n        this.minDate = this.el.dataset.minDate;\n        this.maxDate = this.el.dataset.maxDate;\n        this.type = this.el.dataset.widgetType || \"datetime\";\n    }\n\n    start() {\n        const parseFunction = this.type === \"date\" ? parseDate : parseDateTime;\n        const deserializeFunction = this.type === \"date\" ? deserializeDate : deserializeDateTime;\n        this.registerCleanup(\n            this.services.datetime_picker\n                .create({\n                    target: this.el,\n                    pickerProps: {\n                        type: this.type,\n                        minDate: this.minDate && deserializeFunction(this.minDate),\n                        maxDate: this.maxDate && deserializeFunction(this.maxDate),\n                        value: parseFunction(this.el.value),\n                    },\n                })\n                .enable()\n        );\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"web.datetime_picker\", DatetimePicker);\n", "// This module makes it so that some errors only display a notification instead of an error dialog\n\nimport { registry } from \"@web/core/registry\";\nimport { odooExceptionTitleMap } from \"@web/core/errors/error_dialogs\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nodooExceptionTitleMap.forEach((title, exceptionName) => {\n    registry.category(\"error_notifications\").add(exceptionName, {\n        title: title,\n        type: \"warning\",\n        sticky: true,\n    });\n});\n\nconst sessionExpired = {\n    title: _t(\"Odoo Session Expired\"),\n    message: _t(\"Your Odoo session expired. The current page is about to be refreshed.\"),\n    buttons: [\n        {\n            text: _t(\"Ok\"),\n            click: () => window.location.reload(true),\n            close: true,\n        },\n    ],\n};\n\nregistry\n    .category(\"error_notifications\")\n    .add(\"odoo.http.SessionExpiredException\", sessionExpired)\n    .add(\"werkzeug.exceptions.Forbidden\", sessionExpired)\n    .add(\"504\", {\n        title: _t(\"Request timeout\"),\n        message: _t(\n            \"The operation was interrupted. This usually means that the current operation is taking too much time.\"\n        ),\n    });\n", "import { renderToFragment } from \"@web/core/utils/render\";\nimport { debounce, throttleForAnimation } from \"@web/core/utils/timing\";\nimport { INITIAL_VALUE, SKIP_IMPLICIT_UPDATE } from \"./colibri\";\nimport { makeAsyncHandler, makeButtonHandler } from \"./utils\";\n\n/**\n * This is the base class to describe interactions. The Interaction class\n * provides a good integration with the web framework (env/services), a well\n * specified lifecycle, some dynamic content, and a few helper functions\n * designed to accomplish common tasks, such as adding dom listeners or waiting\n * for some tasks to complete.\n *\n * Note that even though interactions are not destroyed in the standard workflow\n * (a user visiting the website), there are still some cases where it happens:\n * for example, when someone switch the website in \"edit\" mode. This means that\n * interactions should gracefully clean up after themselves.\n */\n\nexport class Interaction {\n    /**\n     * This static property describes the set of html element targeted by this\n     * interaction. An instance will be created for each match when the website\n     * framework is initialized.\n     *\n     * @type {string}\n     */\n    static selector = \"\";\n\n    /**\n     * The `selectorHas` attribute, if defined, allows to filter elements found\n     * through the `selector` attribute by only considering those which contain\n     * at least an element which matches this `selectorHas` selector.\n     *\n     * Note that this is the equivalent of setting up a `selector` using the\n     * `:has` pseudo-selector but that pseudo-selector is known to not be fully\n     * supported in all browsers. To prevent useless crashes, using this\n     * `selectorHas` attribute should be preferred.\n     *\n     * @type {string}\n     */\n    static selectorHas = \"\";\n\n    /**\n     * Similar to `selectorHas` but equivalent to the `:not(:has(...)))`\n     * pseudo-selectors combination.\n     *\n     * Note that both `selectorHas` and `selectorNotHas` can be used\n     * simultaneously.\n     *\n     * @type {string}\n     */\n    static selectorNotHas = \"\";\n\n    /**\n     * Constant to reset dynamicContent t-att-* and t-out.\n     */\n    static INITIAL_VALUE = INITIAL_VALUE;\n\n    /**\n     * Note that a dynamic selector is allowed to return a falsy value, for ex\n     * the result of a querySelector. In that case, the directive will simply be\n     * ignored.\n     *\n     * @type {Object.<string, Function>}\n     */\n    dynamicSelectors = {\n        _root: () => this.el,\n        _body: () => this.el.ownerDocument.body,\n        _window: () => window,\n        _document: () => this.el.ownerDocument,\n    };\n\n    /**\n     * The dynamic content of an interaction is an object describing the set of\n     * \"dynamic elements\" managed by the framework: event handlers, dynamic\n     * attributes, dynamic content, sub components.\n     *\n     * Its syntax looks like the following:\n     * dynamicContent = {\n     *      \".some-selector\": { \"t-on-click\": (ev) => this.onClick(ev) },\n     *      \".some-other-selector\": {\n     *          \"t-att-class\": () => ({ \"some-class\": true }),\n     *          \"t-att-style\": () => ({ property: value }),\n     *          \"t-att-other-attribute\": () => value,\n     *          \"t-out\": () => value,\n     *      },\n     *      _root: { \"t-component\": () => [Component, { someProp: \"value\" }] },\n     * }\n     *\n     * A selector is either a standard css selector, or a special keyword\n     * (see dynamicSelectors: _body, _root, _document, _window)\n     *\n     * Accepted directives include: t-on-, t-att-, t-out and t-component\n     *\n     * A falsy value on a class or style property will remove it.\n     * On others attributes:\n     * - `false`, `undefined` or `null` remove it\n     * - other falsy values (`\"\"`, `0`) are applied as such (`required=\"\"`)\n     * - boolean `true` is applied as the attribute's name\n     *   (e.g. `{ \"t-att-required\": () => true }` applies `required=\"required\"`)\n     *\n     * t-att-* and t-out directives also accept `Interaction.INITIAL_VALUE`,\n     * which resets them to the value they had before the interaction's start.\n     *\n     * Note that this is not owl! It is similar, to make it easy to learn, but\n     * it is different, the syntax and semantics are somewhat different.\n     *\n     * @type {Object}\n     */\n    dynamicContent = {};\n\n    /**\n     * The constructor is not supposed to be defined in a subclass. Use setup\n     * instead.\n     *\n     * @param {HTMLElement} el\n     * @param {import(\"@web/env\").OdooEnv} env\n     * @param {Object} metadata\n     */\n    constructor(el, env, metadata) {\n        this.__colibri__ = metadata;\n        this.el = el;\n        this.env = env;\n        /** @type {import(\"services\").ServiceFactories} */\n        this.services = env.services;\n    }\n\n    /**\n     * Returns true if the interaction has been started (so, just before the\n     * start method is called)\n     */\n    get isReady() {\n        return this.__colibri__.isReady;\n    }\n\n    get isDestroyed() {\n        return this.__colibri__.isDestroyed;\n    }\n\n    // -------------------------------------------------------------------------\n    // lifecycle methods\n    // -------------------------------------------------------------------------\n\n    /**\n     * This is the standard constructor method. This is the proper place to\n     * initialize everything needed by the interaction. The el element is\n     * available and can be used. Services are ready and available as well.\n     */\n    setup() {}\n\n    /**\n     * If the interaction needs some asynchronous work to be ready, it should\n     * be done here. The website framework will wait for this method to complete\n     * before applying the dynamic content (event handlers, ...).\n     */\n    async willStart() {}\n\n    /**\n     * The start function when we need to execute some code once the interaction\n     * is ready. It is the equivalent to the \"mounted\" owl lifecycle hook. At\n     * this point, event handlers have been attached.\n     */\n    start() {}\n\n    /**\n     * All side effects done should be cleaned up here. Note that like all\n     * other lifecycle methods, it is not necessary to call the super.destroy\n     * method (unless you inherit from a concrete subclass).\n     */\n    destroy() {}\n\n    // -------------------------------------------------------------------------\n    // helpers\n    // -------------------------------------------------------------------------\n\n    /**\n     * This method applies the dynamic content description to the dom. So, if\n     * a dynamic attribute has been defined with a t-att-, it will be done\n     * synchronously by this method. Note that updateContent is already being\n     * called after each event handler, and by most other helpers, so this is\n     * not common to need to call it in practice.\n     */\n    updateContent() {\n        this.__colibri__.updateContent();\n    }\n\n    /**\n     * Wraps a promise into a promise that will only be resolved if the instance\n     * has not been destroyed, and will also call `updateContent` after the\n     * calling code has acted.\n     */\n    waitFor(promise = Promise.resolve()) {\n        const prom = new Promise((resolve, reject) => {\n            promise\n                .then((result) => {\n                    if (!this.isDestroyed) {\n                        resolve(result);\n                        prom.then(() => {\n                            if (this.isReady) {\n                                this.updateContent();\n                            }\n                        });\n                    }\n                })\n                .catch((e) => {\n                    reject(e);\n                    prom.catch(() => {\n                        if (this.isReady && !this.isDestroyed) {\n                            this.updateContent();\n                        }\n                    });\n                });\n        });\n        return prom;\n    }\n\n    /**\n     * Mechanism to handle context-specific protection of a specific\n     * chunk of synchronous code after returning from an asynchronous one.\n     * This method returns a function that will run the wrapped function in a\n     * protected context when it is called.\n     * This should typically be used around code that follows an\n     * await this.waitFor(...).\n     *\n     * Example use-case: website builder's edit-mode disables the history\n     * observer to ignore the changes done by interactions.\n     *\n     * A listener involving async code would then look like this:\n     * async onClick() {\n     *     // Code before await is protected\n     *     const result = await this.waitFor(...);\n     *     // Code here is not protected anymore\n     *     // Render variables can be updated because updateContent will run\n     *     // after the handler in a protected state\n     *     this.stuffUsedByTAtt = result.stuffUsedByTAtt;\n     *     this.protectSyncAfterAsync(() => {\n     *         // Code here is protected again, DOM can be updated\n     *         doStuff(this.el);\n     *     })();\n     * }\n     *\n     * @param {Function} fn function that needs to run in a protected context\n     * @return {Function} protected function\n     */\n    protectSyncAfterAsync(fn) {\n        return this.__colibri__.protectSyncAfterAsync(this, \"protectSyncAfterAsync\", fn);\n    }\n\n    /**\n     * Wait for a specific timeout, then execute the given function (unless the\n     * interaction has been destroyed). The dynamic content is then applied.\n     */\n    waitForTimeout(fn, delay) {\n        fn = this.__colibri__.protectSyncAfterAsync(this, \"waitForTimeout\", fn);\n        return setTimeout(() => {\n            if (!this.isDestroyed) {\n                fn.call(this);\n                if (this.isReady) {\n                    this.updateContent();\n                }\n            }\n        }, parseInt(delay));\n    }\n\n    /**\n     * Wait for a animation frame, then execute the given function (unless the\n     * interaction has been destroyed). The dynamic content is then applied.\n     */\n    waitForAnimationFrame(fn) {\n        fn = this.__colibri__.protectSyncAfterAsync(this, \"waitForAnimationFrame\", fn);\n        return window.requestAnimationFrame(() => {\n            if (!this.isDestroyed) {\n                fn.call(this);\n                if (this.isReady) {\n                    this.updateContent();\n                }\n            }\n        });\n    }\n\n    /**\n     * Debounces a function and makes sure it is cancelled upon destroy.\n     */\n    debounced(fn, delay, options) {\n        fn = this.__colibri__.protectSyncAfterAsync(this, \"debounced\", fn);\n        const debouncedFn = debounce(\n            async (...args) => {\n                await fn.apply(this, args);\n                if (this.isReady && !this.isDestroyed) {\n                    this.updateContent();\n                }\n            },\n            delay,\n            options\n        );\n        this.registerCleanup(() => {\n            debouncedFn.cancel();\n        });\n        return Object.assign(\n            {\n                [debouncedFn.name]: (...args) => {\n                    debouncedFn(...args);\n                    return SKIP_IMPLICIT_UPDATE;\n                },\n            }[debouncedFn.name],\n            {\n                cancel: debouncedFn.cancel,\n            }\n        );\n    }\n\n    /**\n     * Throttles a function for animation and makes sure it is cancelled upon\n     * destroy.\n     */\n    throttled(fn) {\n        fn = this.__colibri__.protectSyncAfterAsync(this, \"throttled\", fn);\n        const throttledFn = throttleForAnimation(async (...args) => {\n            await fn.apply(this, args);\n            if (this.isReady && !this.isDestroyed) {\n                this.updateContent();\n            }\n        });\n        this.registerCleanup(() => {\n            throttledFn.cancel();\n        });\n        return Object.assign(\n            {\n                [throttledFn.name]: (...args) => {\n                    throttledFn(...args);\n                    return SKIP_IMPLICIT_UPDATE;\n                },\n            }[throttledFn.name],\n            {\n                cancel: throttledFn.cancel,\n            }\n        );\n    }\n\n    /**\n     * Makes sure the function is not started again before it is completed.\n     * If required, add a loading animation on button if the execution takes\n     * more than 400ms.\n     */\n    locked(fn, useLoadingAnimation = false) {\n        fn = this.__colibri__.protectSyncAfterAsync(this, \"locked\", fn);\n        if (useLoadingAnimation) {\n            return makeButtonHandler(fn);\n        }\n        return makeAsyncHandler(fn);\n    }\n\n    /**\n     * Adds a listener to the target. Whenever the listener is executed, the\n     * dynamic content will be applied. Also, the listener will automatically be\n     * cleaned up when the interaction is destroyed.\n     * Returns a function to remove the listener(s).\n     *\n     * @param {EventTarget|EventTarget[]|NodeList} target one or more element(s) / bus\n     * @param {string} event\n     * @param {Function} fn\n     * @param {Object} [options]\n     * @returns {Function} removes the listeners\n     */\n    addListener(target, event, fn, options) {\n        let nodes;\n        if (target.nodeName && [\"FORM\", \"SELECT\"].includes(target.nodeName)) {\n            nodes = [target];\n        } else {\n            nodes = target[Symbol.iterator] ? target : [target];\n        }\n        const [ev, handler, opts] = this.__colibri__.addListener(nodes, event, fn, options);\n        return () => nodes.forEach((node) => node.removeEventListener(ev, handler, opts));\n    }\n\n    /**\n     * Inserts and activate an element at a specific location (default position:\n     * \"beforeend\").\n     * The inserted element will be removed when the interaction is destroyed.\n     *\n     * @param { HTMLElement } el\n     * @param { HTMLElement } [locationEl] the target\n     * @param { \"afterbegin\" | \"afterend\" | \"beforebegin\" | \"beforeend\" } [position]\n     * @param { boolean } [removeOnClean]\n     */\n    insert(el, locationEl = this.el, position = \"beforeend\", removeOnClean = true) {\n        locationEl.insertAdjacentElement(position, el);\n        if (removeOnClean) {\n            this.registerCleanup(() => el.remove());\n        }\n        this.services[\"public.interactions\"].startInteractions(el);\n        this.__colibri__.refreshNodes();\n    }\n\n    /**\n     * Removes the children of an element.\n     * The children will be inserted back when the interaction is destroyed.\n     *\n     * @param { HTMLElement } el\n     * @param { boolean } [insertBackOnClean]\n     */\n    removeChildren(el, insertBackOnClean = true) {\n        for (const child of el.children) {\n            this.services[\"public.interactions\"].stopInteractions(child);\n        }\n        const children = [...el.childNodes];\n        el.replaceChildren();\n        if (insertBackOnClean) {\n            this.registerCleanup(() => el.replaceChildren(...children));\n        }\n    }\n\n    /**\n     * Renders, inserts and activates an element at a specific location.\n     * The inserted element will be removed when the interaction is destroyed.\n     *\n     * @param { string } template\n     * @param { Object } renderContext\n     * @param { HTMLElement } [locationEl] the target\n     * @param { \"afterbegin\" | \"afterend\" | \"beforebegin\" | \"beforeend\" } [position]\n     * @param { Function } callback called with rendered elements before insertion\n     * @param { boolean } [removeOnClean]\n     * @returns { HTMLElement[] } rendered elements\n     */\n    renderAt(\n        template,\n        renderContext,\n        locationEl,\n        position = \"beforeend\",\n        callback,\n        removeOnClean = true\n    ) {\n        const fragment = renderToFragment(template, renderContext);\n        const result = [...fragment.children];\n        const els = [...fragment.children];\n        callback?.(els);\n        if ([\"afterend\", \"afterbegin\"].includes(position)) {\n            els.reverse();\n        }\n        for (const el of els) {\n            this.insert(el, locationEl, position, removeOnClean);\n        }\n        return result;\n    }\n\n    /**\n     * Registers a function that will be executed when the interaction is\n     * destroyed. It is sometimes useful, so we can explicitly add the cleanup\n     * at the location where the side effect is created.\n     *\n     * @param {Function} fn\n     */\n    registerCleanup(fn) {\n        this.__colibri__.cleanups.push(fn.bind(this));\n    }\n\n    /**\n     * Mounts an Owl component.\n     *\n     * @param {HTMLElement} el\n     * @param {import(\"@odoo/owl\").Component} C\n     * @param {Object|null} [props]\n     * @returns {Function} destroy function for early removal\n     */\n    mountComponent(el, C, props = null, position = \"beforeend\") {\n        return this.__colibri__.mountComponent(el, C, props, position);\n    }\n}\n", "import { registry } from \"@web/core/registry\";\nimport { appTranslateFn } from \"@web/core/l10n/translation\";\nimport { Interaction } from \"./interaction\";\nimport { getTemplate } from \"@web/core/templates\";\nimport { PairSet } from \"./utils\";\nimport { Colibri } from \"./colibri\";\n\n/**\n * Website Core\n *\n * This service handles the core interactions for the website codebase.\n * It will replace public root, publicroot instance, and all that stuff\n *\n * We have 2 kinds of interactions:\n * - simple interactions (subclasses of Interaction)\n * - components\n *\n * The Interaction class is designed to be a simple class that provides access\n * to the framework (env and services), and a minimalist declarative framework\n * that allows manipulating dom, attaching event handlers and updating it\n * properly. It does not depend on owl.\n *\n * The Component kind of interaction is used for more complicated interface needs.\n * It provides full access to Owl features, but is rendered browser side.\n *\n */\n\nclass InteractionService {\n    /**\n     *\n     * @param {HTMLElement} el\n     * @param {Object} env\n     */\n    constructor(el, env) {\n        this.Interactions = [];\n        this.el = el;\n        this.isActive = false;\n        // relation el <--> Interaction\n        this.activeInteractions = new PairSet();\n        this.env = env;\n        this.interactions = [];\n        this.roots = [];\n        this.owlApp = null;\n        this.proms = [];\n        this.registry = null;\n    }\n\n    /**\n     *\n     * @param {Interaction[]} Interactions\n     * @param {HTMLElement} target - The target element where interactions need\n     *                               to be activated.\n     */\n    activate(Interactions, target) {\n        this.Interactions = Interactions;\n        const startProm = this.env.isReady.then(() => this.startInteractions(target));\n        this.proms.push(startProm);\n    }\n\n    prepareRoot(el, C, props, position = \"beforeend\") {\n        if (!this.owlApp) {\n            const { App } = odoo.loader.modules.get(\"@odoo/owl\");\n            const appConfig = {\n                name: \"Odoo Website\",\n                getTemplate,\n                env: this.env,\n                dev: this.env.debug,\n                translateFn: appTranslateFn,\n                warnIfNoStaticProps: this.env.debug,\n                translatableAttributes: [\"data-tooltip\"],\n            };\n            this.owlApp = new App(null, appConfig);\n        }\n        const root = this.owlApp.createRoot(C, { props, env: this.env });\n        const rootEl = document.createElement(\"owl-root\");\n        rootEl.setAttribute(\"contenteditable\", \"false\");\n        rootEl.dataset.oeProtected = \"true\";\n        rootEl.style.display = \"contents\";\n        el.insertAdjacentElement(position, rootEl);\n        return {\n            C,\n            root,\n            el: rootEl,\n            mount: () => root.mount(rootEl),\n            destroy: () => {\n                root.destroy();\n                rootEl.remove();\n            },\n        };\n    }\n\n    async _mountComponent(el, C) {\n        const root = this.prepareRoot(el, C);\n        this.roots.push(root);\n        return root.mount();\n    }\n\n    startInteractions(el = this.el) {\n        if (!el.isConnected) {\n            return Promise.resolve();\n        }\n        const proms = [];\n        for (const I of this.Interactions) {\n            if (I.selector === \"\") {\n                throw new Error(\n                    `The selector should be defined as a static property on the class ${I.name}, not on the instance`\n                );\n            }\n            if (I.dynamicContent) {\n                throw new Error(\n                    `The dynamic content object should be defined on the instance, not on the class (${I.name})`\n                );\n            }\n            let targets;\n            try {\n                const isMatch = el.matches(I.selector);\n                targets = isMatch\n                    ? [el, ...el.querySelectorAll(I.selector)]\n                    : el.querySelectorAll(I.selector);\n                if (I.selectorHas) {\n                    targets = [...targets].filter((el) => !!el.querySelector(I.selectorHas));\n                }\n                if (I.selectorNotHas) {\n                    targets = [...targets].filter((el) => !el.querySelector(I.selectorNotHas));\n                }\n            } catch {\n                const selectorHasError = I.selectorHas ? ` or selectorHas: '${I.selectorHas}'` : \"\";\n                const selectorNotHasError = I.selectorNotHas\n                    ? ` or selectorNotHas: '${I.selectorNotHas}'`\n                    : \"\";\n                const error = new Error(\n                    `Could not start interaction ${I.name} (invalid selector: '${I.selector}'${selectorHasError}${selectorNotHasError})`\n                );\n                proms.push(Promise.reject(error));\n                continue;\n            }\n            for (const _el of targets) {\n                this._startInteraction(_el, I, proms);\n            }\n        }\n        if (el === this.el) {\n            this.isActive = true;\n        }\n        const prom = Promise.all(proms);\n        this.proms.push(prom);\n        return prom;\n    }\n\n    _startInteraction(el, I, proms) {\n        if (this.activeInteractions.has(el, I)) {\n            return;\n        }\n        this.activeInteractions.add(el, I);\n        if (I.prototype instanceof Interaction) {\n            try {\n                const interaction = new Colibri(this, I, el);\n                this.interactions.push(interaction);\n                proms.push(interaction.start());\n            } catch (e) {\n                this.proms.push(Promise.reject(e));\n            }\n        } else {\n            proms.push(this._mountComponent(el, I));\n        }\n    }\n\n    shouldStop(el, interaction) {\n        const { selectorNotHas, selectorHas } = interaction.interaction.constructor;\n        if (!interaction.el) {\n            return true;\n        }\n        return (\n            el === interaction.el ||\n            el.contains(interaction.el) ||\n            (selectorHas && !interaction.el.querySelector(selectorHas)) ||\n            (selectorNotHas && !!interaction.el.querySelector(selectorNotHas))\n        );\n    }\n\n    stopInteractions(el = this.el) {\n        const interactions = [];\n        for (const interaction of this.interactions.slice().reverse()) {\n            if (this.shouldStop(el, interaction)) {\n                interaction.destroy();\n                this.activeInteractions.delete(interaction.el, interaction.interaction.constructor);\n            } else {\n                interactions.push(interaction);\n            }\n        }\n        this.interactions = interactions;\n        const roots = [];\n        for (const root of this.roots.slice().reverse()) {\n            if (el === root.el || el.contains(root.el)) {\n                root.destroy();\n                this.activeInteractions.delete(root.el, root.C);\n            } else {\n                roots.push(root);\n            }\n        }\n        this.roots = roots;\n        if (el === this.el) {\n            this.isActive = false;\n        }\n    }\n\n    /**\n     * @returns { Promise } returns a promise that is resolved when all current\n     * interactions are started. Note that it does not take into account possible\n     * future interactions.\n     */\n    get isReady() {\n        const proms = this.proms.slice();\n        return Promise.all(proms);\n    }\n}\n\nexport const publicInteractionService = {\n    dependencies: [\"localization\"],\n    async start(env) {\n        // fallback if #wrapwrap is not present in the dom\n        const el = document.querySelector(\"#wrapwrap\") || document.querySelector(\"body\");\n        const Interactions = registry.category(\"public.interactions\").getAll();\n        const service = new InteractionService(el, env);\n        service.activate(Interactions);\n        return service;\n    },\n};\n\nregistry.category(\"services\").add(\"public.interactions\", publicInteractionService);\n", "import { Interaction } from \"./interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { addLoadingEffect } from \"@web/core/utils/ui\";\n\nexport class Login extends Interaction {\n    static selector = \".oe_login_form\";\n    dynamicContent = {\n        _root: { \"t-on-submit\": this.onSubmit },\n    };\n\n    /**\n     * Prevents the user from crazy clicking:\n     * Gives the button a loading effect if preventDefault was not already\n     * called and modifies the preventDefault function of the event so that the\n     * loading effect is removed if preventDefault() is called in a following\n     * customization.\n     *\n     * @param {Event} ev\n     */\n    onSubmit(ev) {\n        if (!ev.defaultPrevented) {\n            const submitEl = ev.currentTarget.querySelector(\"button[type='submit']\");\n            const removeLoadingEffect = addLoadingEffect(submitEl);\n            const oldPreventDefault = ev.preventDefault.bind(ev);\n            ev.preventDefault = () => {\n                removeLoadingEffect();\n                oldPreventDefault();\n            };\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"public.login\", Login);\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"./interaction\";\n\nexport class PublicComponentInteraction extends Interaction {\n    static selector = \"owl-component[name]\";\n\n    setup() {\n        const props = JSON.parse(this.el.getAttribute(\"props\") || \"{}\");\n        // clear owl-component content to make sure we don't have any leftover\n        // html from a previous page edit, where owl-components were not properly\n        // cleaned up while saving\n        this.el.replaceChildren();\n        this.mountComponent(this.el, this.Component, props);\n    }\n\n    get Component() {\n        const name = this.el.getAttribute(\"name\");\n        return registry.category(\"public_components\").get(name);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"public_components\", PublicComponentInteraction);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class ShowPassword extends Interaction {\n    static selector = \".input-group\";\n    static selectorHas = \":scope > .o_show_password\";\n    dynamicContent = {\n        \".o_show_password\": {\n            \"t-on-click\": () => this.showPassword = !this.showPassword,\n        },\n        \"input[type='text'], input[type='password']\": {\n            \"t-att-type\": () => this.showPassword ? \"text\" : \"password\",\n        },\n        \".o_show_password > i\": {\n            \"t-att-class\": () => ({\n                \"fa-eye\": !this.showPassword,\n                \"fa-eye-slash\": !!this.showPassword,\n            }),\n        },\n    };\n}\n\nregistry.category(\"public.interactions\").add(\"web.show_password\", ShowPassword);\n", "export class PairSet {\n    constructor() {\n        this.map = new Map(); // map of [1] => Set<[2]>\n    }\n    add(elem1, elem2) {\n        if (!this.map.has(elem1)) {\n            this.map.set(elem1, new Set());\n        }\n        this.map.get(elem1).add(elem2);\n    }\n    has(elem1, elem2) {\n        if (!this.map.has(elem1)) {\n            return false;\n        }\n        return this.map.get(elem1).has(elem2);\n    }\n    delete(elem1, elem2) {\n        if (!this.map.has(elem1)) {\n            return;\n        }\n        const s = this.map.get(elem1);\n        s.delete(elem2);\n        if (!s.size) {\n            this.map.delete(elem1);\n        }\n    }\n}\n\nimport { addLoadingEffect } from \"@web/core/utils/ui\";\n\nexport const DEBOUNCE = 400;\nexport const BUTTON_HANDLER_SELECTOR =\n    'a, button, input[type=\"submit\"], input[type=\"button\"], .btn';\n\n/**\n * Protects a function which is to be used as a handler by preventing its\n * execution for the duration of a previous call to it (including async\n * parts of that call).\n *\n * @param {function} fct\n *      The function which is to be used as a handler. If a promise\n *      is returned, it is used to determine when the handler's action is\n *      finished. Otherwise, the return is used as jQuery uses it.\n */\nexport function makeAsyncHandler(fct) {\n    let pending = false;\n    function _isLocked() {\n        return pending;\n    }\n    function _lock() {\n        pending = true;\n    }\n    function _unlock() {\n        pending = false;\n    }\n    return function () {\n        if (_isLocked()) {\n            // If a previous call to this handler is still pending, ignore\n            // the new call.\n            return;\n        }\n\n        _lock();\n        const result = fct.apply(this, arguments);\n        Promise.resolve(result).finally(_unlock);\n        return result;\n    };\n}\n\n/**\n * Creates a debounced version of a function to be used as a button click\n * handler. Also improves the handler to disable the button for the time of\n * the debounce and/or the time of the async actions it performs.\n *\n * Limitation: if two handlers are put on the same button, the button will\n * become enabled again once any handler's action finishes (multiple click\n * handlers should however not be bound to the same button).\n *\n * @param {function} fct\n *      The function which is to be used as a button click handler. If a\n *      promise is returned, it is used to determine when the button can be\n *      re-enabled. Otherwise, the return is used as jQuery uses it.\n */\nexport function makeButtonHandler(fct) {\n    // Fallback: if the final handler is not bound to a button, at least\n    // make it an async handler (also handles the case where some events\n    // might ignore the disabled state of the button).\n    fct = makeAsyncHandler(fct);\n\n    return function (ev) {\n        const result = fct.apply(this, arguments);\n\n        const buttonEl = ev.target.closest(BUTTON_HANDLER_SELECTOR);\n        if (!(buttonEl instanceof HTMLElement)) {\n            return result;\n        }\n\n        // Disable the button for the duration of the handler's action\n        // or at least for the duration of the click debounce. This makes\n        // a 'real' debounce creation useless. Also, during the debouncing\n        // part, the button is disabled without any visual effect.\n        buttonEl.classList.add(\"pe-none\");\n        new Promise((resolve) => setTimeout(resolve, DEBOUNCE)).then(() => {\n            buttonEl.classList.remove(\"pe-none\");\n            const restore = addLoadingEffect(buttonEl);\n            return Promise.resolve(result).then(restore, restore);\n        });\n\n        return result;\n    };\n}\n\n/**\n * Patches a \"t-\" entry of a dynamic content.\n *\n * @param {Object} dynamicContent\n * @param {string} selector\n * @param {string} t\n * @param {any|function} replacement, if a function, takes the element and the\n *     replaced's function output as parameters\n */\nexport function patchDynamicContentEntry(dynamicContent, selector, t, replacement) {\n    dynamicContent[selector] = dynamicContent[selector] || {};\n    const forSelector = dynamicContent[selector];\n    if (replacement === undefined) {\n        delete forSelector[t];\n    } else if (typeof replacement === \"function\" && t !== \"t-component\") {\n        if (!forSelector[t]) {\n            forSelector[t] = () => {};\n        }\n        const oldFn = forSelector[t];\n        if ([\"t-att-class\", \"t-att-style\"].includes(t)) {\n            forSelector[t] = (el, oldResult) => {\n                const result = oldResult || {};\n                Object.assign(result, oldFn(el, result));\n                Object.assign(result, replacement(el, result));\n                return result;\n            };\n        } else if (t.startsWith(\"t-on-\")) {\n            forSelector[t] = (el, ...args) => replacement(el, oldFn, ...args);\n        } else {\n            forSelector[t] = (el, oldResult) => {\n                let result = oldResult;\n                result = oldFn(el, result);\n                result = replacement(el, result);\n                return result;\n            };\n        }\n    } else {\n        forSelector[t] = replacement;\n    }\n}\n\n/**\n * Patches several entries in a dynamicContent.\n * Example usage:\n * patchDynamicContent(this.dynamicContent, {\n *     _root: {\n *         \"t-att-class\": (el, old) => ({\n *             \"test\": this.condition && old.test,\n *         }),\n *         \"t-on-click\": (el, oldFn) => {\n *             oldFn(el);\n *             this.doMoreStuff();\n *         },\n *     },\n * })\n *\n * @param {Object} dynamicContent\n * @param {Object} replacement\n */\nexport function patchDynamicContent(dynamicContent, replacement) {\n    for (const [selector, forSelector] of Object.entries(replacement)) {\n        for (const [t, forT] of Object.entries(forSelector)) {\n            patchDynamicContentEntry(dynamicContent, selector, t, forT);\n        }\n    }\n}\n", "import { cookie } from \"@web/core/browser/cookie\";\nimport publicWidget from '@web/legacy/js/public/public_widget';\n\nimport lazyloader from \"@web/legacy/js/public/lazyloader\";\n\nimport { makeEnv, startServices } from \"@web/env\";\nimport { getTemplate } from '@web/core/templates';\nimport { MainComponentsContainer } from \"@web/core/main_components_container\";\nimport { browser } from '@web/core/browser/browser';\nimport { appTranslateFn } from \"@web/core/l10n/translation\";\nimport { jsToPyLocale, pyToJsLocale } from \"@web/core/l10n/utils\";\nimport { App, Component, whenReady } from \"@odoo/owl\";\nimport { RPCError } from '@web/core/network/rpc';\nimport { patch } from \"@web/core/utils/patch\";\n\nconst { Settings } = luxon;\n\n// Load localizations outside the PublicRoot to not wait for DOM ready (but\n// wait for them in PublicRoot)\nfunction getLang() {\n    var html = document.documentElement;\n    return jsToPyLocale(html.getAttribute('lang')) || 'en_US';\n}\nconst lang = cookie.get('frontend_lang') || getLang(); // FIXME the cookie value should maybe be in the ctx?\n\n\n/**\n * Element which is designed to be unique and that will be the top-most element\n * in the widget hierarchy. So, all other widgets will be indirectly linked to\n * this Class instance. Its main role will be to retrieve RPC demands from its\n * children and handle them.\n */\nexport const PublicRoot = publicWidget.Widget.extend({\n    events: {\n        'submit .js_website_submit_form': '_onWebsiteFormSubmit',\n        'click .js_disable_on_click': '_onDisableOnClick',\n    },\n    custom_events: {\n        call_service: '_onCallService',\n        context_get: '_onContextGet',\n        main_object_request: '_onMainObjectRequest',\n        widgets_start_request: '_onWidgetsStartRequest',\n        widgets_stop_request: '_onWidgetsStopRequest',\n    },\n\n    /**\n     * @constructor\n     */\n    init: function (_, env) {\n        this._super.apply(this, arguments);\n        this.env = env;\n        this.publicWidgets = [];\n        // Patch interaction_service so that it also starts and stops public\n        // widgets.\n        const interactionsService = this.env.services[\"public.interactions\"];\n        const publicRoot = this;\n        if (interactionsService) {\n            patch(interactionsService.constructor.prototype, {\n                startInteractions(el) {\n                    super.startInteractions(el);\n                    if (!publicRoot.startFromEventHandler) {\n                        // this.editMode is assigned by website_edit_service\n                        publicRoot._startWidgets($(el || this.el), { fromInteractionPatch: true, editableMode: this.editMode })\n                    }\n                },\n                stopInteractions(el) {\n                    super.stopInteractions(el);\n                    // Call to interactions is only from the event handler.\n                    if (!publicRoot.stopFromEventHandler) {\n                        publicRoot._stopWidgets($(el || this.el));\n                    }\n                },\n            });\n        }\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var defs = [\n            this._super.apply(this, arguments),\n            this._startWidgets(undefined, { starting: true })\n        ];\n\n        // Display image thumbnail\n        this.$(\".o_image[data-mimetype^='image']\").each(function () {\n            var $img = $(this);\n            if (/gif|jpe|jpg|png|webp/.test($img.data('mimetype')) && $img.data('src')) {\n                $img.css('background-image', \"url('\" + $img.data('src') + \"')\");\n            }\n        });\n\n        // Auto scroll\n        if (window.location.hash.indexOf(\"scrollTop=\") > -1) {\n            this.el.scrollTop = +window.location.hash.match(/scrollTop=([0-9]+)/)[1];\n        }\n\n        return Promise.all(defs);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Retrieves the global context of the public environment. This is the\n     * context which is automatically added to each RPC.\n     *\n     * @private\n     * @param {Object} [context]\n     * @returns {Object}\n     */\n    _getContext: function (context) {\n        return Object.assign({\n            'lang': getLang(),\n        }, context || {});\n    },\n    /**\n     * Retrieves the global context of the public environment (as\n     * @see _getContext) but with extra informations that would be useless to\n     * send with each RPC.\n     *\n     * @private\n     * @param {Object} [context]\n     * @returns {Object}\n     */\n    _getExtraContext: function (context) {\n        return this._getContext(context);\n    },\n    /**\n     * @private\n     * @param {Object} [options]\n     * @returns {Object}\n     */\n    _getPublicWidgetsRegistry: function (options) {\n        return publicWidget.registry;\n    },\n    /**\n     * Restarts interactions from the specified targetEl, or from #wrapwrap.\n     *\n     * @private\n     * @param {HTMLElement} targetEl\n     * @param {Object} [options]\n     */\n    _restartInteractions(targetEl, options) {\n        const publicInteractions = this.bindService(\"public.interactions\");\n        publicInteractions.stopInteractions(targetEl);\n        publicInteractions.startInteractions(targetEl);\n    },\n    /**\n     * Creates an PublicWidget instance for each DOM element which matches the\n     * `selector` key of one of the registered widgets\n     * (@see PublicWidget.selector).\n     *\n     * @private\n     * @param {jQuery} [$from]\n     *        only initialize the public widgets whose `selector` matches the\n     *        element or one of its descendant (default to the wrapwrap element)\n     * @param {Object} [options]\n     * @returns {Deferred}\n     */\n    _startWidgets: function ($from, options) {\n        var self = this;\n\n        if ($from === undefined) {\n            $from = this.$('#wrapwrap');\n            if (!$from.length) {\n                // TODO Remove this once all frontend layouts possess a\n                // #wrapwrap element (which is necessary for those pages to be\n                // adapted correctly if the user installs website).\n                $from = this.$el;\n            }\n        }\n        options = Object.assign({}, options, {\n            wysiwyg: $('#wrapwrap').data('wysiwyg'),\n        });\n\n        this._stopWidgets($from);\n        if (!options?.starting && !options?.fromInteractionPatch) {\n            if ($from) {\n                for (const fromEl of $from) {\n                    this._restartInteractions(fromEl, options);\n                }\n            } else {\n                this._restartInteractions(undefined, options);\n            }\n        }\n\n        var defs = Object.values(this._getPublicWidgetsRegistry(options)).map((PublicWidget) => {\n            const selector = PublicWidget.prototype.selector;\n            if (!selector) {\n                return;\n            }\n            const selectorHas = PublicWidget.prototype.selectorHas;\n            const selectorFunc = typeof selector === 'function'\n                ? selector\n                : fromEl => {\n                    const els = [...fromEl.querySelectorAll(selector)];\n                    if (fromEl.matches(selector)) {\n                        els.push(fromEl);\n                    }\n                    return els;\n                };\n\n            let targetEls = [];\n            for (const fromEl of $from) {\n                targetEls.push(...selectorFunc(fromEl));\n            }\n            if (selectorHas) {\n                targetEls = targetEls.filter(el => !!el.querySelector(selectorHas));\n            }\n\n            const proms = targetEls.map(el => {\n                var widget = new PublicWidget(self, options);\n                self.publicWidgets.push(widget);\n                return widget.attachTo(el);\n            });\n            return Promise.all(proms);\n        });\n        return Promise.all(defs);\n    },\n    /**\n     * Destroys all registered widget instances. Website would need this before\n     * saving while in edition mode for example.\n     *\n     * @private\n     * @param {jQuery} [$from]\n     *        only stop the public widgets linked to the given element(s) or one\n     *        of its descendants\n     */\n    _stopWidgets: function ($from) {\n        var removedWidgets = this.publicWidgets.map((widget) => {\n            if (!$from\n                || $from.filter(widget.el).length\n                || $from.find(widget.el).length) {\n                widget.destroy();\n                return widget;\n            }\n            return null;\n        });\n        this.publicWidgets = this.publicWidgets.filter((x) => removedWidgets.indexOf(x) < 0);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Calls the requested service from the env. Automatically adds the global\n     * context to RPCs.\n     *\n     * @private\n     * @param {OdooEvent} event\n     */\n    _onCallService: function (ev) {\n        const payload = ev.data;\n        const service = this.env.services[payload.service];\n        const result = service[payload.method].apply(service, payload.args || []);\n        payload.callback(result);\n        ev.stopPropagation();\n    },\n    /**\n     * Called when someone asked for the global public context.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onContextGet: function (ev) {\n        if (ev.data.extra) {\n            ev.data.callback(this._getExtraContext(ev.data.context));\n        } else {\n            ev.data.callback(this._getContext(ev.data.context));\n        }\n    },\n    /**\n     * Checks information about the page main object.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onMainObjectRequest: function (ev) {\n        var repr = $('html').data('main-object');\n        var m = repr.match(/(.+)\\((-?\\d+),(.*)\\)/);\n        ev.data.callback({\n            model: m[1],\n            id: m[2] | 0,\n        });\n    },\n    /**\n     * Called when the root is notified that the public widgets have to be\n     * (re)started.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    async _onWidgetsStartRequest(ev) {\n        this.startFromEventHandler = true;\n        try {\n            await this._startWidgets(ev.data.$target, ev.data.options);\n            ev.data.onSuccess?.();\n        } catch (e) {\n            ev.data.onFailure?.(e);\n            if (!(e instanceof RPCError)) {\n                throw e;\n            }\n        } finally {\n            this.stopFromEventHandler = true;\n        }\n    },\n    /**\n     * Called when the root is notified that the public widgets have to be\n     * stopped.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onWidgetsStopRequest: function (ev) {\n        this._stopWidgets(ev.data.$target);\n        // also stops interactions\n        const targetEl = ev.data.$target ? ev.data.$target[0] : undefined;\n        const publicInteractions = this.bindService(\"public.interactions\");\n        this.stopFromEventHandler = true;\n        try {\n            publicInteractions.stopInteractions(targetEl);\n        } finally {\n            this.stopFromEventHandler = false;\n        }\n    },\n    /**\n     * @todo review\n     * @private\n     */\n    _onWebsiteFormSubmit: function (ev) {\n        var $buttons = $(ev.currentTarget).find('button[type=\"submit\"], a.a-submit').toArray();\n        $buttons.forEach((btn) => {\n            var $btn = $(btn);\n            $btn.prepend('<i class=\"fa fa-circle-o-notch fa-spin\"></i> ');\n            $btn.prop('disabled', true);\n        });\n    },\n    /**\n     * Called when the root is notified that the button should be\n     * disabled after the first click.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onDisableOnClick: function (ev) {\n        $(ev.currentTarget).addClass('disabled');\n    },\n    /**\n     * Library clears the wrong date format so just ignore error\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onDateTimePickerError: function (ev) {\n        return false;\n    },\n});\n\n/**\n * This widget is important, because the tour manager needs a root widget in\n * order to work. The root widget must be a service provider with the ajax\n * service, so that the tour manager can let the server know when tours have\n * been consumed.\n */\nexport async function createPublicRoot(RootWidget) {\n    await lazyloader.allScriptsLoaded;\n    await whenReady();\n    const env = makeEnv();\n    await startServices(env);\n\n    env.services[\"public.interactions\"].isReady.then(() => {\n        document.body.setAttribute(\"is-ready\", \"true\");\n    });\n\n    Component.env = env;\n    const publicRoot = new RootWidget(null, env);\n    const app = new App(MainComponentsContainer, {\n        getTemplate,\n        env,\n        dev: env.debug,\n        translateFn: appTranslateFn,\n        translatableAttributes: [\"data-tooltip\"],\n    });\n    const locale = pyToJsLocale(lang) || browser.navigator.language;\n    Settings.defaultLocale = locale;\n    const [root] = await Promise.all([\n        app.mount(document.body),\n        publicRoot.attachTo(document.body),\n    ]);\n    odoo.__WOWL_DEBUG__ = { root };\n    return publicRoot;\n}\n\nexport default { PublicRoot, createPublicRoot };\n", "/** @odoo-module alias=root.widget */\n\nimport { createPublicRoot } from \"@web/legacy/js/public/public_root\";\nimport lazyloader from \"@web/legacy/js/public/lazyloader\";\nimport { WebsiteRoot } from \"./website_root\";\n\nconst prom = createPublicRoot(WebsiteRoot).then(async (rootInstance) => {\n    if (window.frameElement) {\n        window.dispatchEvent(new CustomEvent(\"PUBLIC-ROOT-READY\", { detail: { rootInstance } }));\n    }\n    return rootInstance;\n});\nlazyloader.registerPageReadinessDelay(prom);\nexport default prom;\n", "/**\n * Provides a way to start JS code for public contents.\n */\n\nimport { Component } from \"@odoo/owl\";\nimport Class from \"@web/legacy/js/core/class\";\nimport { loadBundle, loadCSS, loadJS } from '@web/core/assets';\nimport { SERVICES_METADATA } from \"@web/core/utils/hooks\";\nimport { renderToElement } from \"@web/core/utils/render\";\nimport { makeAsyncHandler, makeButtonHandler } from \"@web/legacy/js/public/minimal_dom\";\n\n/**\n * Mixin to structure objects' life-cycles following a parent-children\n * relationship. Each object can a have a parent and multiple children.\n * When an object is destroyed, all its children are destroyed too releasing\n * any resource they could have reserved before.\n *\n * @name ParentedMixin\n * @mixin\n */\nconst ParentedMixin = {\n    __parentedMixin: true,\n\n    init: function () {\n        this.__parentedDestroyed = false;\n        this.__parentedChildren = [];\n        this.__parentedParent = null;\n    },\n    /**\n     * Set the parent of the current object. When calling this method, the\n     * parent will also be informed and will return the current object\n     * when its getChildren() method is called. If the current object did\n     * already have a parent, it is unregistered before, which means the\n     * previous parent will not return the current object anymore when its\n     * getChildren() method is called.\n     */\n    setParent(parent) {\n        if (this.getParent()) {\n            if (this.getParent().__parentedMixin) {\n                const children = this.getParent().getChildren();\n                this.getParent().__parentedChildren = children.filter(\n                    (child) => child.$el !== this.$el\n                );\n            }\n        }\n        this.__parentedParent = parent;\n        if (parent && parent.__parentedMixin) {\n            parent.__parentedChildren.push(this);\n        }\n    },\n    /**\n     * Return the current parent of the object (or null).\n     */\n    getParent() {\n        return this.__parentedParent;\n    },\n    /**\n     * Return a list of the children of the current object.\n     */\n    getChildren() {\n        return [...this.__parentedChildren];\n    },\n    /**\n     * Returns true if destroy() was called on the current object.\n     */\n    isDestroyed() {\n        return this.__parentedDestroyed;\n    },\n    /**\n     * Releases any resource the instance could have reserved.\n     */\n    destroy() {\n        this.getChildren().forEach(function (child) {\n            child.destroy();\n        });\n        this.setParent(undefined);\n        this.__parentedDestroyed = true;\n    },\n};\n\nfunction OdooEvent(target, name, data) {\n    this.target = target;\n    this.name = name;\n    this.data = Object.create(null);\n    Object.assign(this.data, data);\n    this.stopped = false;\n}\nOdooEvent.prototype.stopPropagation = function () {\n    this.stopped = true;\n};\nOdooEvent.prototype.is_stopped = function () {\n    return this.stopped;\n};\n\n/**\n * Do not ever use it directly, use EventDispatcherMixin instead. This class\n * just handles the dispatching of events, it is not meant to be extended, nor\n * used directly. All integration with parenting and automatic unregistration of\n * events is done in EventDispatcherMixin.\n *\n * Copyright notice for the following Class and its uses:\n *\n * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.\n * Backbone may be freely distributed under the MIT license.\n * For all details and documentation:\n * http://backbonejs.org\n *\n * See the debian/copyright file for the text of the MIT license.\n */\nclass Events {\n    on(events, callback, context) {\n        var ev;\n        events = events.split(/\\s+/);\n        var calls = this._callbacks || (this._callbacks = {});\n        while ((ev = events.shift())) {\n            var list = calls[ev] || (calls[ev] = {});\n            var tail = list.tail || (list.tail = list.next = {});\n            tail.callback = callback;\n            tail.context = context;\n            list.tail = tail.next = {};\n        }\n        return this;\n    }\n    off(events, callback, context) {\n        var ev, calls, node;\n        if (!events) {\n            delete this._callbacks;\n        } else if ((calls = this._callbacks)) {\n            events = events.split(/\\s+/);\n            while ((ev = events.shift())) {\n                node = calls[ev];\n                delete calls[ev];\n                if (!callback || !node) {\n                    continue;\n                }\n                while ((node = node.next) && node.next) {\n                    if (node.callback === callback\n                            && (!context || node.context === context)) {\n                        continue;\n                    }\n                    this.on(ev, node.callback, node.context);\n                }\n            }\n        }\n        return this;\n    }\n    callbackList() {\n        var lst = [];\n        for (const [eventName, el] of Object.entries(this._callbacks || {})) {\n            var node = el;\n            while ((node = node.next) && node.next) {\n                lst.push([eventName, node.callback, node.context]);\n            }\n        }\n        return lst;\n    }\n    trigger(events) {\n        var event, node, calls, tail, args, all, rest;\n        if (!(calls = this._callbacks)) {\n            return this;\n        }\n        all = calls.all;\n        (events = events.split(/\\s+/)).push(null);\n        // Save references to the current heads & tails.\n        while ((event = events.shift())) {\n            if (all) {\n                events.push({\n                    next: all.next,\n                    tail: all.tail,\n                    event: event\n                });\n            }\n            if (!(node = calls[event])) {\n                continue;\n            }\n            events.push({\n                next: node.next,\n                tail: node.tail\n            });\n        }\n        rest = Array.prototype.slice.call(arguments, 1);\n        while ((node = events.pop())) {\n            tail = node.tail;\n            args = node.event ? [node.event].concat(rest) : rest;\n            while ((node = node.next) !== tail) {\n                node.callback.apply(node.context || this, args);\n            }\n        }\n        return this;\n    }\n}\n\n/**\n * Mixin containing an event system. Events are also registered by specifying\n * the target object (the object which will receive the event when raised). Both\n * the event-emitting object and the target object store or reference to each\n * other. This is used to correctly remove all reference to the event handler\n * when any of the object is destroyed (when the destroy() method from\n * ParentedMixin is called). Removing those references is necessary to avoid\n * memory leak and phantom events (events which are raised and sent to a\n * previously destroyed object).\n *\n * @name EventDispatcherMixin\n * @mixin\n */\nconst EventDispatcherMixin = Object.assign({}, ParentedMixin, {\n    __eventDispatcherMixin: true,\n    \"custom_events\": {},\n\n    init() {\n        ParentedMixin.init.call(this);\n        this.__edispatcherEvents = new Events();\n        this.__edispatcherRegisteredEvents = [];\n        this._delegateCustomEvents();\n    },\n    /**\n     * Proxies a method of the object, in order to keep the right ``this`` on\n     * method invocations.\n     *\n     * This method is similar to ``Function.prototype.bind``, and\n     * even more so to ``jQuery.proxy`` with a fundamental difference: its\n     * resolution of the method being called is lazy, meaning it will use the\n     * method as it is when the proxy is called, not when the proxy is created.\n     *\n     * Other methods will fix the bound method to what it is when creating the\n     * binding/proxy, which is fine in most javascript code but problematic in\n     * Odoo where developers may want to replace existing callbacks with theirs.\n     *\n     * The semantics of this precisely replace closing over the method call.\n     *\n     * @param {String|Function} method function or name of the method to invoke\n     * @returns {Function} proxied method\n     */\n    proxy(method) {\n        var self = this;\n        return function () {\n            var fn = (typeof method === 'string') ? self[method] : method;\n            if (fn === void 0) {\n                throw new Error(\"Couldn't find method '\" + method + \"' in widget \" + self);\n            }\n            return fn.apply(self, arguments);\n        };\n    },\n    _delegateCustomEvents() {\n        if (Object.keys(this.custom_events || {}).length === 0) {\n            return;\n        }\n        for (var key in this.custom_events) {\n            if (!Object.prototype.hasOwnProperty.call(this.custom_events, key)) {\n                continue;\n            }\n\n            var method = this.proxy(this.custom_events[key]);\n            this.on(key, this, method);\n        }\n    },\n    on(events, dest, func) {\n        var self = this;\n        if (typeof func !== \"function\") {\n            throw new Error(\"Event handler must be a function.\");\n        }\n        events = events.split(/\\s+/);\n        events.forEach((eventName) => {\n            self.__edispatcherEvents.on(eventName, func, dest);\n            if (dest && dest.__eventDispatcherMixin) {\n                dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});\n            }\n        });\n        return this;\n    },\n    off(events, dest, func) {\n        var self = this;\n        events = events.split(/\\s+/);\n        events.forEach((eventName) => {\n            self.__edispatcherEvents.off(eventName, func, dest);\n            if (dest && dest.__eventDispatcherMixin) {\n                dest.__edispatcherRegisteredEvents = dest.__edispatcherRegisteredEvents.filter(el => {\n                    return !(el.name === eventName && el.func === func && el.source === self);\n                });\n            }\n        });\n        return this;\n    },\n    trigger() {\n        this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);\n        return this;\n    },\n    \"trigger_up\": function (name, info) {\n        var event = new OdooEvent(this, name, info);\n        //console.info('event: ', name, info);\n        this._trigger_up(event);\n        return event;\n    },\n    \"_trigger_up\": function (event) {\n        var parent;\n        this.__edispatcherEvents.trigger(event.name, event);\n        if (!event.is_stopped() && (parent = this.getParent())) {\n            parent._trigger_up(event);\n        }\n    },\n    destroy() {\n        var self = this;\n        this.__edispatcherRegisteredEvents.forEach((event) => {\n            event.source.__edispatcherEvents.off(event.name, event.func, self);\n        });\n        this.__edispatcherRegisteredEvents = [];\n        this.__edispatcherEvents.callbackList().forEach(\n            ((cal) => {\n                this.off(cal[0], cal[2], cal[1]);\n            }).bind(this)\n        );\n        this.__edispatcherEvents.off();\n        ParentedMixin.destroy.call(this);\n    },\n});\n\nfunction protectMethod(widget, fn) {\n    return function (...args) {\n        return new Promise((resolve, reject) => {\n            Promise.resolve(fn.call(this, ...args))\n                .then((result) => {\n                    if (!widget.isDestroyed()) {\n                        resolve(result);\n                    }\n                })\n                .catch((reason) => {\n                    if (!widget.isDestroyed()) {\n                        reject(reason);\n                    }\n                });\n        });\n    };\n}\n\nconst ServicesMixin = {\n    bindService: function (serviceName) {\n        const { services } = Component.env;\n        const service = services[serviceName];\n        if (!service) {\n            throw new Error(`Service ${serviceName} is not available`);\n        }\n        if (serviceName in SERVICES_METADATA) {\n            if (service instanceof Function) {\n                return protectMethod(this, service);\n            } else {\n                const methods = SERVICES_METADATA[serviceName];\n                const result = Object.create(service);\n                for (const method of methods) {\n                    result[method] = protectMethod(this, service[method]);\n                }\n                return result;\n            }\n        }\n        return service;\n    },\n    /**\n     * @param  {string} service\n     * @param  {string} method\n     * @return {any} result of the service called\n     */\n    call: function (service, method) {\n        var args = Array.prototype.slice.call(arguments, 2);\n        var result;\n        this.trigger_up('call_service', {\n            service: service,\n            method: method,\n            args: args,\n            callback: function (r) {\n                result = r;\n            },\n        });\n        return result;\n    },\n};\n\n/**\n * Base class for all visual components. Provides a lot of functions helpful\n * for the management of a part of the DOM.\n *\n * Widget handles:\n *\n * - Rendering with QWeb.\n * - Life-cycle management and parenting (when a parent is destroyed, all its\n *   children are destroyed too).\n * - Insertion in DOM.\n *\n * **Guide to create implementations of the Widget class**\n *\n * Here is a sample child class::\n *\n *     var MyWidget = Widget.extend({\n *         // the name of the QWeb template to use for rendering\n *         template: \"MyQWebTemplate\",\n *\n *         init: function (parent) {\n *             this._super(parent);\n *             // stuff that you want to init before the rendering\n *         },\n *         willStart: function () {\n *             // async work that need to be done before the widget is ready\n *             // this method should return a promise\n *         },\n *         start: function() {\n *             // stuff you want to make after the rendering, `this.$el` holds a correct value\n *             this.$(\".my_button\").click(/* an example of event binding * /);\n *\n *             // if you have some asynchronous operations, it's a good idea to return\n *             // a promise in start(). Note that this is quite rare, and if you\n *             // need to fetch some data, this should probably be done in the\n *             // willStart method\n *             var promise = this._rpc(...);\n *             return promise;\n *         }\n *     });\n *\n * Now this class can simply be used with the following syntax::\n *\n *     var myWidget = new MyWidget(this);\n *     myWidget.appendTo($(\".some-div\"));\n *\n * With these two lines, the MyWidget instance was initialized, rendered,\n * inserted into the DOM inside the ``.some-div`` div and its events were\n * bound.\n *\n * This class can also be initialized and started on an existing DOM element\n * using the `selector` property. See below for more documentation.\n *\n * And of course, when you don't need that widget anymore, just do::\n *\n *     myWidget.destroy();\n *\n * That will kill the widget in a clean way and erase its content from the dom.\n *\n * This class also provides a way for executing code once a website DOM element\n * is loaded in the dom.\n * @see PublicWidget.selector\n */\nexport const PublicWidget = Class.extend(EventDispatcherMixin, ServicesMixin, {\n    // Backbone-ish API\n    tagName: 'div',\n    id: null,\n    className: null,\n    attributes: {},\n    /**\n     * The name of the QWeb template that will be used for rendering. Must be\n     * redefined in subclasses or the default render() method can not be used.\n     *\n     * @type {null|string}\n     */\n    template: null,\n    /**\n     * List of paths to css files that need to be loaded before the widget can\n     * be rendered. This will not induce loading anything that has already been\n     * loaded.\n     *\n     * @type {null|string[]}\n     */\n    cssLibs: null,\n    /**\n     * List of paths to js files that need to be loaded before the widget can\n     * be rendered. This will not induce loading anything that has already been\n     * loaded.\n     *\n     * @type {null|string[]}\n     */\n    jsLibs: null,\n    /**\n     * List of xmlID that need to be loaded before the widget can be rendered.\n     * The content css (link file or style tag) and js (file or inline) of the\n     * assets are loaded.\n     * This will not induce loading anything that has already been\n     * loaded.\n     *\n     * @type {null|string[]}\n     */\n    assetLibs: null,\n    /**\n     * The selector attribute, if defined, allows to automatically create an\n     * instance of this widget on page load for each DOM element according to\n     * this selector. The `PublicWidget.$el / el` element will then be that\n     * particular DOM element. This should be the main way of instantiating\n     * `PublicWidget` elements.\n     *\n     * The value can either be a string in which case it is considered as a\n     * `querySelectorAll` selector to match, or a function expecting to return\n     * all DOM elements to consider, which are inside the element received as\n     * parameter of the function (or that element itself).\n     *\n     * @see selectorHas\n     *\n     * @todo do not make this part of the Widget but rather an info to give when\n     * registering the widget.\n     *\n     * @type {string|function|false}\n     */\n    selector: false,\n    /**\n     * The `selectorHas` attribute, if defined, allows to filter elements found\n     * through the `selector` attribute by only considering those which contain\n     * at least an element which matches this `selectorHas` selector.\n     *\n     * Note that this is the equivalent of setting up a `selector` using the\n     * `:has` pseudo-selector but that pseudo-selector is known to not be fully\n     * supported in all browsers. To prevent useless crashes, using this\n     * `selectorHas` attribute should be preferred.\n     *\n     * @type {string|false}\n     */\n    selectorHas: false,\n    /**\n     * Extension of @see Widget.events\n     *\n     * A description of the event handlers to bind/delegate once the widget\n     * has been rendered::\n     *\n     *   'click .hello .world': 'async _onHelloWorldClick',\n     *     _^_      _^_           _^_        _^_\n     *      |        |             |          |\n     *      |  (Optional) jQuery   |  Handler method name\n     *      |  delegate selector   |\n     *      |                      |_ (Optional) space separated options\n     *      |                          * async: use the automatic system\n     *      |_ Event name with           making handlers promise-ready (see\n     *         potential jQuery          makeButtonHandler, makeAsyncHandler)\n     *         namespaces\n     *\n     * Note: the values may be replaced by a function declaration. This is\n     * however a deprecated behavior.\n     *\n     * @type {Object}\n     */\n    events: {},\n\n    /**\n     * @constructor\n     * @param {Object} parent\n     * @param {Object} [options]\n     */\n    init: function (parent, options) {\n        EventDispatcherMixin.init.call(this);\n        this.setParent(parent);\n        this.options = options || {};\n    },\n    /**\n     * Method called between @see init and @see start. Performs asynchronous\n     * calls required by the rendering and the start method.\n     *\n     * This method should return a Promise which is resolved when start can be\n     * executed.\n     *\n     * @returns {Promise}\n     */\n    willStart: function () {\n        var proms = [];\n        if (this.jsLibs || this.cssLibs || this.assetLibs) {\n            var assetsPromise = Promise.all([\n                ...(this.cssLibs || []).map(loadCSS),\n                ...(this.jsLibs || []).map(loadJS),\n            ]);\n            for (const bundleName of this.assetLibs || []) {\n                if (typeof bundleName === \"string\") {\n                    assetsPromise = assetsPromise.then(() => {\n                        return loadBundle(bundleName);\n                    });\n                } else {\n                    assetsPromise = assetsPromise.then(() => {\n                        return Promise.all([...bundleName.map(loadBundle)]);\n                    });\n                }\n            }\n            proms.push(assetsPromise);\n        }\n        return Promise.all(proms);\n    },\n    /**\n     * Method called after rendering. Mostly used to bind actions, perform\n     * asynchronous calls, etc...\n     *\n     * By convention, this method should return an object that can be passed to\n     * Promise.resolve() to inform the caller when this widget has been initialized.\n     *\n     * Note that, for historic reasons, many widgets still do work in the start\n     * method that would be more suited to the willStart method.\n     *\n     * @returns {Promise}\n     */\n    start: function () {\n        return Promise.resolve();\n    },\n    /**\n     * Destroys the widget and basically restores the target to the state it\n     * was before the start method was called (unlike standard widget, the\n     * associated $el DOM is not removed, if this was instantiated thanks to the\n     * selector property).\n     */\n    destroy: function () {\n        EventDispatcherMixin.destroy.call(this);\n        if (this.$el) {\n            this._undelegateEvents();\n\n            // If not done with a selector (attached to existing DOM), then\n            // remove the elements added to the DOM.\n            if (!this.selector) {\n                this.$el.remove();\n            }\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Renders the current widget and appends it to the given jQuery object.\n     *\n     * @param {jQuery} target\n     * @returns {Promise}\n     */\n    appendTo: function (target) {\n        var self = this;\n        return this._widgetRenderAndInsert(function (t) {\n            self.$el.appendTo(t);\n        }, target);\n    },\n    /**\n     * Attach the current widget to a dom element\n     *\n     * @param {jQuery} target\n     * @returns {Promise}\n     */\n    attachTo: function (target) {\n        var self = this;\n        this.setElement(target.$el || target);\n        return this.willStart().then(function () {\n            if (self.__parentedDestroyed) {\n                return;\n            }\n            return self.start();\n        });\n    },\n    /**\n     * Renders the current widget and inserts it after to the given jQuery\n     * object.\n     *\n     * @param {jQuery} target\n     * @returns {Promise}\n     */\n    insertAfter: function (target) {\n        var self = this;\n        return this._widgetRenderAndInsert(function (t) {\n            self.$el.insertAfter(t);\n        }, target);\n    },\n    /**\n     * Renders the current widget and inserts it before to the given jQuery\n     * object.\n     *\n     * @param {jQuery} target\n     * @returns {Promise}\n     */\n    insertBefore: function (target) {\n        var self = this;\n        return this._widgetRenderAndInsert(function (t) {\n            self.$el.insertBefore(t);\n        }, target);\n    },\n    /**\n     * Renders the current widget and prepends it to the given jQuery object.\n     *\n     * @param {jQuery} target\n     * @returns {Promise}\n     */\n    prependTo: function (target) {\n        var self = this;\n        return this._widgetRenderAndInsert(function (t) {\n            self.$el.prependTo(t);\n        }, target);\n    },\n    /**\n     * Renders the element. The default implementation renders the widget using\n     * QWeb, `this.template` must be defined. The context given to QWeb contains\n     * the \"widget\" key that references `this`.\n     */\n    renderElement: function () {\n        var $el;\n        if (this.template) {\n            $el = $(renderToElement(this.template, {widget: this}));\n        } else {\n            $el = this._makeDescriptive();\n        }\n        this._replaceElement($el);\n    },\n    /**\n     * Renders the current widget and replaces the given jQuery object.\n     *\n     * @param target A jQuery object or a Widget instance.\n     * @returns {Promise}\n     */\n    replace: function (target) {\n        return this._widgetRenderAndInsert((t) => {\n            this.$el.replaceAll(t);\n        }, target);\n    },\n    /**\n     * Re-sets the widget's root element (el/$el/$el).\n     *\n     * Includes:\n     *\n     * * re-delegating events\n     * * re-binding sub-elements\n     * * if the widget already had a root element, replacing the pre-existing\n     *   element in the DOM\n     *\n     * @param {HTMLElement | jQuery} element new root element for the widget\n     * @return {Widget} this\n     */\n    setElement: function (element) {\n        if (this.$el) {\n            this._undelegateEvents();\n        }\n\n        this.$el = (element instanceof $) ? element : $(element);\n        this.el = this.$el[0];\n\n        this._delegateEvents();\n\n        if (this.selector) {\n            this.$target = this.$el;\n            this.target = this.el;\n        }\n\n        return this;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Helper method, for ``this.$el.find(selector)``\n     *\n     * @private\n     * @param {string} selector CSS selector, rooted in $el\n     * @returns {jQuery} selector match\n     */\n    $: function (selector) {\n        if (selector === undefined) {\n            return this.$el;\n        }\n        return this.$el.find(selector);\n    },\n    /**\n     * @see this.events\n     * @override\n     */\n    _delegateEvents: function () {\n        var self = this;\n\n        const _delegateEvent = (method, key) => {\n            var match = /^(\\S+)(\\s+(.*))?$/.exec(key);\n            var event = match[1];\n            var selector = match[3];\n\n            event += '.widget_events';\n            if (!selector) {\n                self.$el.on(event, method);\n            } else {\n                self.$el.on(event, selector, method);\n            }\n        };\n        Object.entries(this.events || {}).forEach(([event, method]) => {\n            // If the method is a function, use the default Widget system\n            if (typeof method !== 'string') {\n                _delegateEvent(self.proxy(method), event);\n                return;\n            }\n            // If the method is only a function name without options, use the\n            // default Widget system\n            var methodOptions = method.split(' ');\n            if (methodOptions.length <= 1) {\n                _delegateEvent(self.proxy(method), event);\n                return;\n            }\n            // If the method has no meaningful options, use the default Widget\n            // system\n            var isAsync = methodOptions.includes('async');\n            if (!isAsync) {\n                _delegateEvent(self.proxy(method), event);\n                return;\n            }\n\n            method = self.proxy(methodOptions[methodOptions.length - 1]);\n            if (String(event).startsWith(\"click\")) {\n                // Protect click handler to be called multiple times by\n                // mistake by the user and add a visual disabling effect\n                // for buttons.\n                method = makeButtonHandler(method);\n            } else {\n                // Protect all handlers to be recalled while the previous\n                // async handler call is not finished.\n                method = makeAsyncHandler(method);\n            }\n            _delegateEvent(method, event);\n        });\n    },\n    /**\n     * @private\n     * @param {boolean} [extra=false]\n     * @param {Object} [extraContext]\n     * @returns {Object}\n     */\n    _getContext: function (extra, extraContext) {\n        var context;\n        this.trigger_up('context_get', {\n            extra: extra || false,\n            context: extraContext,\n            callback: function (ctx) {\n                context = ctx;\n            },\n        });\n        return context;\n    },\n    /**\n     * Makes a potential root element from the declarative builder of the\n     * widget\n     *\n     * @private\n     * @return {jQuery}\n     */\n    _makeDescriptive: function () {\n        var attrs = Object.assign({}, this.attributes || {});\n        if (this.id) {\n            attrs.id = this.id;\n        }\n        if (this.className) {\n            attrs['class'] = this.className;\n        }\n        var $el = $(document.createElement(this.tagName));\n        if (Object.keys(attrs || {}).length > 0) {\n            $el.attr(attrs);\n        }\n        return $el;\n    },\n    /**\n     * Re-sets the widget's root element and replaces the old root element\n     * (if any) by the new one in the DOM.\n     *\n     * @private\n     * @param {HTMLElement | jQuery} $el\n     * @returns {Widget} this instance, so it can be chained\n     */\n    _replaceElement: function ($el) {\n        var $oldel = this.$el;\n        this.setElement($el);\n        if ($oldel && !$oldel.is(this.$el)) {\n            if ($oldel.length > 1) {\n                $oldel.wrapAll('<div/>');\n                $oldel.parent().replaceWith(this.$el);\n            } else {\n                $oldel.replaceWith(this.$el);\n            }\n        }\n        return this;\n    },\n    /**\n     * Remove all handlers registered on this.$el\n     *\n     * @private\n     */\n    _undelegateEvents: function () {\n        this.$el.off('.widget_events');\n    },\n    /**\n     * Render the widget.  This is a private method, and should really never be\n     * called by anyone (except this widget).  It assumes that the widget was\n     * not willStarted yet.\n     *\n     * @private\n     * @param {function: jQuery -> any} insertion\n     * @param {jQuery} target\n     * @returns {Promise}\n     */\n    _widgetRenderAndInsert: function (insertion, target) {\n        var self = this;\n        return this.willStart().then(function () {\n            if (self.__parentedDestroyed) {\n                return;\n            }\n            self.renderElement();\n            insertion(target);\n            return self.start();\n        });\n    },\n});\n\n//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n\n/**\n * The registry object contains the list of widgets that should be instantiated\n * thanks to their selector property if any.\n */\nvar registry = {};\n\nexport default {\n    Widget: PublicWidget,\n    registry: registry,\n\n    ParentedMixin: ParentedMixin,\n    EventDispatcherMixin: EventDispatcherMixin,\n    ServicesMixin: ServicesMixin,\n};\n", "import { registry } from \"@web/core/registry\";\n\nexport const busParametersService = {\n    start() {\n        return {\n            serverURL: window.origin,\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"bus.parameters\", busParametersService);\n", "import { registry } from \"@web/core/registry\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { EventBus } from \"@odoo/owl\";\n\nexport const legacyMultiTabService = {\n    start() {\n        const bus = new EventBus();\n\n        // PROPERTIES\n        const sanitizedOrigin = location.origin.replace(/:\\/{0,2}/g, \"_\");\n        const localStoragePrefix = `${this.name}.${sanitizedOrigin}.`;\n\n        function generateLocalStorageKey(baseKey) {\n            return localStoragePrefix + baseKey;\n        }\n\n        function getItemFromStorage(key, defaultValue) {\n            const item = browser.localStorage.getItem(generateLocalStorageKey(key));\n            try {\n                return item ? JSON.parse(item) : defaultValue;\n            } catch {\n                return item;\n            }\n        }\n\n        function setItemInStorage(key, value) {\n            browser.localStorage.setItem(generateLocalStorageKey(key), JSON.stringify(value));\n        }\n\n        function onStorage({ key, newValue }) {\n            if (key && key.includes(localStoragePrefix)) {\n                // Only trigger the shared_value_updated event if the key is\n                // related to this service/origin.\n                const baseKey = key.replace(localStoragePrefix, \"\");\n                bus.trigger(\"shared_value_updated\", { key: baseKey, newValue });\n            }\n        }\n\n        browser.addEventListener(\"storage\", onStorage);\n\n        return {\n            bus,\n            generateLocalStorageKey,\n            getItemFromStorage,\n            setItemInStorage,\n            /**\n             * Get value shared between all the tabs.\n             *\n             * @param {string} key\n             * @param {any} defaultValue Value to be returned if this\n             * key does not exist.\n             */\n            getSharedValue(key, defaultValue) {\n                return getItemFromStorage(key, defaultValue);\n            },\n            /**\n             * Set value shared between all the tabs.\n             *\n             * @param {string} key\n             * @param {any} value\n             */\n            setSharedValue(key, value) {\n                if (value === undefined) {\n                    return this.removeSharedValue(key);\n                }\n                setItemInStorage(key, value);\n            },\n            /**\n             * Remove value shared between all the tabs.\n             *\n             * @param {string} key\n             */\n            removeSharedValue(key) {\n                browser.localStorage.removeItem(generateLocalStorageKey(key));\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"legacy_multi_tab\", legacyMultiTabService);\n", "import { browser } from \"@web/core/browser/browser\";\n\n/**\n * Returns a function, that, when invoked, will only be triggered at most once\n * during a given window of time. Normally, the throttled function will run\n * as much as it can, without ever going more than once per `wait` duration;\n * but if you'd like to disable the execution on the leading edge, pass\n * `{leading: false}`. To disable execution on the trailing edge, ditto.\n *\n * credit to `underscore.js`\n */\nfunction throttle(func, wait, options) {\n    let timeout, context, args, result;\n    let previous = 0;\n    if (!options) {\n        options = {};\n    }\n\n    const later = function () {\n        previous = options.leading === false ? 0 : luxon.DateTime.now().ts;\n        timeout = null;\n        result = func.apply(context, args);\n        if (!timeout) {\n            context = args = null;\n        }\n    };\n\n    const throttled = function () {\n        const _now = luxon.DateTime.now().ts;\n        if (!previous && options.leading === false) {\n            previous = _now;\n        }\n        const remaining = wait - (_now - previous);\n        context = this;\n        args = arguments;\n        if (remaining <= 0 || remaining > wait) {\n            if (timeout) {\n                browser.clearTimeout(timeout);\n                timeout = null;\n            }\n            previous = _now;\n            result = func.apply(context, args);\n            if (!timeout) {\n                context = args = null;\n            }\n        } else if (!timeout && options.trailing !== false) {\n            timeout = browser.setTimeout(later, remaining);\n        }\n        return result;\n    };\n\n    throttled.cancel = function () {\n        browser.clearTimeout(timeout);\n        previous = 0;\n        timeout = context = args = null;\n    };\n\n    return throttled;\n}\n\nexport const timings = {\n    throttle,\n};\n", "import { browser } from \"@web/core/browser/browser\";\nimport { EventBus } from \"@odoo/owl\";\n\nlet multiTabId = 0;\n/**\n * This service uses a Master/Slaves with Leader Election architecture in\n * order to keep track of the main tab. Tabs are synchronized thanks to the\n * localStorage.\n *\n * localStorage used keys are:\n * - multi_tab_service.lastPresenceByTab: mapping of tab ids to their last\n *   recorded presence.\n * - multi_tab_service.main: a boolean indicating whether a main tab is already\n *   present.\n * - multi_tab_service.heartbeat: last main tab heartbeat time.\n *\n * trigger:\n * - become_main_tab : when this tab became the main.\n * - no_longer_main_tab : when this tab is no longer the main.\n */\nexport const multiTabFallbackService = {\n    start(env) {\n        const bus = new EventBus();\n\n        // CONSTANTS\n        const TAB_HEARTBEAT_PERIOD = 10000; // 10 seconds\n        const MAIN_TAB_HEARTBEAT_PERIOD = 1500; // 1.5 seconds\n        const HEARTBEAT_OUT_OF_DATE_PERIOD = 5000; // 5 seconds\n        const HEARTBEAT_KILL_OLD_PERIOD = 15000; // 15 seconds\n\n        // PROPERTIES\n        let _isOnMainTab = false;\n        let lastHeartbeat = 0;\n        let heartbeatTimeout;\n        const now = new Date().getTime();\n        const tabId = `${this.name}${multiTabId++}:${now}`;\n\n        function startElection() {\n            if (_isOnMainTab) {\n                return;\n            }\n            // Check who's next.\n            const now = new Date().getTime();\n            const lastPresenceByTab =\n                JSON.parse(localStorage.getItem(\"multi_tab_service.lastPresenceByTab\")) ?? {};\n            const heartbeatKillOld = now - HEARTBEAT_KILL_OLD_PERIOD;\n            let newMain;\n            for (const [tab, lastPresence] of Object.entries(lastPresenceByTab)) {\n                // Check for dead tabs.\n                if (lastPresence < heartbeatKillOld) {\n                    continue;\n                }\n                newMain = tab;\n                break;\n            }\n            if (newMain === tabId) {\n                // We're next in queue. Electing as main.\n                lastHeartbeat = now;\n                localStorage.setItem(\"multi_tab_service.heartbeat\", lastHeartbeat);\n                localStorage.setItem(\"multi_tab_service.main\", true);\n                _isOnMainTab = true;\n                bus.trigger(\"become_main_tab\");\n                // Removing main peer from queue.\n                delete lastPresenceByTab[newMain];\n                localStorage.setItem(\n                    \"multi_tab_service.lastPresenceByTab\",\n                    JSON.stringify(lastPresenceByTab)\n                );\n            }\n        }\n\n        function heartbeat() {\n            const now = new Date().getTime();\n            let heartbeatValue = parseInt(localStorage.getItem(\"multi_tab_service.heartbeat\") ?? 0);\n            const lastPresenceByTab =\n                JSON.parse(localStorage.getItem(\"multi_tab_service.lastPresenceByTab\")) ?? {};\n            if (heartbeatValue + HEARTBEAT_OUT_OF_DATE_PERIOD < now) {\n                // Heartbeat is out of date. Electing new main.\n                startElection();\n                heartbeatValue = parseInt(localStorage.getItem(\"multi_tab_service.heartbeat\") ?? 0);\n            }\n            if (_isOnMainTab) {\n                // Walk through all tabs and kill old ones.\n                const cleanedTabs = {};\n                for (const [tabId, lastPresence] of Object.entries(lastPresenceByTab)) {\n                    if (lastPresence + HEARTBEAT_KILL_OLD_PERIOD > now) {\n                        cleanedTabs[tabId] = lastPresence;\n                    }\n                }\n                if (heartbeatValue !== lastHeartbeat) {\n                    // Someone else is also main...\n                    // It should not happen, except in some race condition situation.\n                    _isOnMainTab = false;\n                    lastHeartbeat = 0;\n                    lastPresenceByTab[tabId] = now;\n                    localStorage.setItem(\n                        \"multi_tab_service.lastPresenceByTab\",\n                        JSON.stringify(lastPresenceByTab)\n                    );\n                    bus.trigger(\"no_longer_main_tab\");\n                } else {\n                    lastHeartbeat = now;\n                    localStorage.setItem(\"multi_tab_service.heartbeat\", now);\n                    localStorage.setItem(\n                        \"multi_tab_service.lastPresenceByTab\",\n                        JSON.stringify(cleanedTabs)\n                    );\n                }\n            } else {\n                // Update own heartbeat.\n                lastPresenceByTab[tabId] = now;\n                localStorage.setItem(\n                    \"multi_tab_service.lastPresenceByTab\",\n                    JSON.stringify(lastPresenceByTab)\n                );\n            }\n            const hbPeriod = _isOnMainTab ? MAIN_TAB_HEARTBEAT_PERIOD : TAB_HEARTBEAT_PERIOD;\n            heartbeatTimeout = browser.setTimeout(heartbeat, hbPeriod);\n        }\n\n        function onStorage({ key, newValue }) {\n            if (key === \"multi_tab_service.main\" && !newValue) {\n                // Main was unloaded.\n                startElection();\n            }\n        }\n\n        /**\n         * Unregister this tab from the multi-tab service. It will no longer\n         * be able to become the main tab.\n         */\n        function unregister() {\n            clearTimeout(heartbeatTimeout);\n            const lastPresenceByTab =\n                JSON.parse(localStorage.getItem(\"multi_tab_service.lastPresenceByTab\")) ?? {};\n            delete lastPresenceByTab[tabId];\n            localStorage.setItem(\n                \"multi_tab_service.lastPresenceByTab\",\n                JSON.stringify(lastPresenceByTab)\n            );\n\n            // Unload main.\n            if (_isOnMainTab) {\n                _isOnMainTab = false;\n                bus.trigger(\"no_longer_main_tab\");\n                browser.localStorage.removeItem(\"multi_tab_service.main\");\n            }\n        }\n\n        browser.addEventListener(\"pagehide\", unregister);\n        browser.addEventListener(\"storage\", onStorage);\n\n        // REGISTER THIS TAB\n        const lastPresenceByTab =\n            JSON.parse(localStorage.getItem(\"multi_tab_service.lastPresenceByTab\")) ?? {};\n        lastPresenceByTab[tabId] = now;\n        localStorage.setItem(\n            \"multi_tab_service.lastPresenceByTab\",\n            JSON.stringify(lastPresenceByTab)\n        );\n\n        if (!localStorage.getItem(\"multi_tab_service.main\")) {\n            startElection();\n        }\n        heartbeat();\n\n        return {\n            bus,\n            /**\n             * Determine whether or not this tab is the main one.\n             * it's intentionally an async function to match the API of\n             * multiTabSharedWorkerService\n             *\n             * @returns {boolean}\n             */\n            async isOnMainTab() {\n                return _isOnMainTab;\n            },\n            /**\n             * Unregister this tab from the multi-tab service. It will no longer\n             * be able to become the main tab.\n             */\n            unregister,\n        };\n    },\n};\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { multiTabFallbackService } from \"@bus/multi_tab_fallback_service\";\nimport { multiTabSharedWorkerService } from \"@bus/multi_tab_shared_worker_service\";\n\nexport const multiTabService = browser.SharedWorker\n    ? multiTabSharedWorkerService\n    : multiTabFallbackService;\n\nregistry.category(\"services\").add(\"multi_tab\", multiTabService);\n", "import { browser } from \"@web/core/browser/browser\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { EventBus } from \"@odoo/owl\";\n\nconst STATE = Object.freeze({\n    INIT: \"INIT\",\n    MASTER: \"MASTER\",\n    REGISTERED: \"REGISTERED\",\n    UNREGISTERED: \"UNREGISTERED\",\n});\n\nexport const multiTabSharedWorkerService = {\n    dependencies: [\"worker_service\"],\n    start(env, { worker_service: workerService }) {\n        const bus = new EventBus();\n        let responseDeferred = null;\n        let state = STATE.INIT;\n        browser.addEventListener(\"pagehide\", unregister);\n\n        function messageHandler(messageEv) {\n            const { type, data } = messageEv.data;\n            if (!type?.startsWith(\"ELECTION:\")) {\n                return;\n            }\n            switch (type) {\n                case \"ELECTION:IS_MASTER_RESPONSE\":\n                    responseDeferred?.resolve(data.answer);\n                    responseDeferred = null;\n                    break;\n                case \"ELECTION:HEARTBEAT_REQUEST\":\n                    workerService.send(\"ELECTION:HEARTBEAT\");\n                    break;\n                case \"ELECTION:ASSIGN_MASTER\":\n                    state = STATE.MASTER;\n                    bus.trigger(\"become_main_tab\");\n                    break;\n                case \"ELECTION:UNASSIGN_MASTER\":\n                    if (state !== STATE.UNREGISTERED) {\n                        state = STATE.REGISTERED;\n                    }\n                    bus.trigger(\"no_longer_main_tab\");\n                    break;\n                default:\n                    console.warn(\n                        \"multiTabSharedWorkerService received unknown message type:\",\n                        type\n                    );\n            }\n        }\n\n        async function startWorker() {\n            await workerService.ensureWorkerStarted();\n            await workerService.registerHandler(messageHandler);\n            workerService.send(\"ELECTION:REGISTER\");\n            state = STATE.REGISTERED;\n        }\n\n        function unregister() {\n            workerService.send(\"ELECTION:UNREGISTER\");\n            state = STATE.UNREGISTERED;\n        }\n\n        return {\n            bus,\n            isOnMainTab: async () => {\n                if (state === STATE.UNREGISTERED) {\n                    return false;\n                }\n                if (state === STATE.INIT) {\n                    await startWorker();\n                }\n                responseDeferred = new Deferred();\n                workerService.send(\"ELECTION:IS_MASTER?\");\n                return responseDeferred;\n            },\n            unregister,\n        };\n    },\n};\n", "import { browser } from \"@web/core/browser/browser\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { registry } from \"@web/core/registry\";\n\nexport class OutdatedPageWatcherService {\n    constructor(env, services) {\n        this.setup(env, services);\n    }\n\n    /**\n     * @param {import(\"@web/env\").OdooEnv}\n     * @param {Partial<import(\"services\").Services>} services\n     */\n    setup(env, { bus_service, multi_tab, legacy_multi_tab, notification }) {\n        this.notification = notification;\n        this.multi_tab = multi_tab;\n        this.legacy_multi_tab = legacy_multi_tab;\n        this.lastNotificationId = legacy_multi_tab.getSharedValue(\"last_notification_id\");\n        this.closeNotificationFn;\n        let wasBusAlreadyConnected;\n        bus_service.addEventListener(\n            \"BUS:WORKER_STATE_UPDATED\",\n            ({ detail: state }) => {\n                wasBusAlreadyConnected = state !== \"IDLE\";\n            },\n            { once: true }\n        );\n        bus_service.addEventListener(\n            \"BUS:DISCONNECT\",\n            () =>\n                (this.lastNotificationId = legacy_multi_tab.getSharedValue(\"last_notification_id\"))\n        );\n        bus_service.addEventListener(\"BUS:CONNECT\", async () => {\n            if (wasBusAlreadyConnected) {\n                this.checkHasMissedNotifications();\n            }\n            wasBusAlreadyConnected = true;\n        });\n        bus_service.addEventListener(\"BUS:RECONNECT\", () => this.checkHasMissedNotifications());\n        legacy_multi_tab.bus.addEventListener(\"shared_value_updated\", ({ detail: { key } }) => {\n            if (key === \"bus.has_missed_notifications\") {\n                this.showOutdatedPageNotification();\n            }\n        });\n    }\n\n    async checkHasMissedNotifications() {\n        if (!this.lastNotificationId || !(await this.multi_tab.isOnMainTab())) {\n            return;\n        }\n        const hasMissedNotifications = await rpc(\n            \"/bus/has_missed_notifications\",\n            { last_notification_id: this.lastNotificationId },\n            { silent: true }\n        );\n        if (hasMissedNotifications) {\n            this.showOutdatedPageNotification();\n            this.legacy_multi_tab.setSharedValue(\"bus.has_missed_notifications\", Date.now());\n        }\n    }\n\n    showOutdatedPageNotification() {\n        this.closeNotificationFn?.();\n        this.closeNotificationFn = this.notification.add(\n            _t(\"Save your work and refresh to get the latest updates and avoid potential issues.\"),\n            {\n                title: _t(\"The page is out of date\"),\n                type: \"warning\",\n                sticky: true,\n                buttons: [\n                    {\n                        name: _t(\"Refresh\"),\n                        primary: true,\n                        onClick: () => browser.location.reload(),\n                    },\n                ],\n            }\n        );\n    }\n}\n\nexport const outdatedPageWatcherService = {\n    dependencies: [\"bus_service\", \"multi_tab\", \"legacy_multi_tab\", \"notification\"],\n    start(env, services) {\n        return new OutdatedPageWatcherService(env, services);\n    },\n};\n\nregistry.category(\"services\").add(\"bus.outdated_page_watcher\", outdatedPageWatcherService);\n", "import { WORKER_STATE } from \"@bus/workers/websocket_worker\";\nimport { reactive } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * Detect lost connections to the bus. A connection is considered as lost if it\n * couldn't be established after a reconnect attempt.\n */\nexport class BusMonitoringService {\n    isConnectionLost = false;\n\n    constructor(env, services) {\n        const reactiveThis = reactive(this);\n        reactiveThis.setup(env, services);\n        return reactiveThis;\n    }\n\n    /**\n     * @param {import(\"@web/env\").OdooEnv} env\n     * @param {Partial<import(\"services\").Services>} services\n     */\n    setup(env, { bus_service }) {\n        bus_service.addEventListener(\"BUS:WORKER_STATE_UPDATED\", ({ detail }) =>\n            this.workerStateOnChange(detail)\n        );\n        browser.addEventListener(\"offline\", () => (this.isReconnecting = false));\n    }\n\n    /**\n     * Handle state changes for the WebSocket worker.\n     *\n     * @param {WORKER_STATE[keyof WORKER_STATE]} state\n     */\n    workerStateOnChange(state) {\n        switch (state) {\n            case WORKER_STATE.CONNECTING: {\n                this.isReconnecting = true;\n                break;\n            }\n            case WORKER_STATE.CONNECTED: {\n                this.isReconnecting = false;\n                this.isConnectionLost = false;\n                break;\n            }\n            case WORKER_STATE.DISCONNECTED: {\n                if (this.isReconnecting) {\n                    this.isConnectionLost = true;\n                    this.isReconnecting = false;\n                }\n                break;\n            }\n        }\n    }\n}\n\nexport const busMonitoringservice = {\n    dependencies: [\"bus_service\"],\n    start(env, services) {\n        return new BusMonitoringService(env, services);\n    },\n};\n\nregistry.category(\"services\").add(\"bus.monitoring_service\", busMonitoringservice);\n", "import { browser } from \"@web/core/browser/browser\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { registry } from \"@web/core/registry\";\nimport { session } from \"@web/session\";\nimport { EventBus, reactive } from \"@odoo/owl\";\nimport { user } from \"@web/core/user\";\n\n// List of worker events that should not be broadcasted.\nconst INTERNAL_EVENTS = new Set([\n    \"BUS:INITIALIZED\",\n    \"BUS:OUTDATED\",\n    \"BUS:NOTIFICATION\",\n    \"BUS:PROVIDE_LOGS\",\n]);\n// Slightly delay the reconnection when coming back online as the network is not\n// ready yet and the exponential backoff would delay the reconnection by a lot.\nexport const BACK_ONLINE_RECONNECT_DELAY = 5000;\n/**\n * Communicate with a SharedWorker in order to provide a single websocket\n * connection shared across multiple tabs.\n *\n *  @emits BUS:CONNECT\n *  @emits BUS:DISCONNECT\n *  @emits BUS:RECONNECT\n *  @emits BUS:RECONNECTING\n *  @emits BUS:WORKER_STATE_UPDATED\n */\nexport const busService = {\n    dependencies: [\n        \"bus.parameters\",\n        \"localization\",\n        \"multi_tab\",\n        \"legacy_multi_tab\",\n        \"notification\",\n        \"worker_service\",\n    ],\n\n    start(\n        env,\n        {\n            multi_tab: multiTab,\n            legacy_multi_tab: legacyMultiTab,\n            notification,\n            \"bus.parameters\": params,\n            worker_service: workerService,\n        }\n    ) {\n        const bus = new EventBus();\n        const notificationBus = new EventBus();\n        const subscribeFnToWrapper = new Map();\n        let backOnlineTimeout;\n        const startedAt = luxon.DateTime.now().set({ milliseconds: 0 });\n        let connectionInitializedDeferred;\n\n        /**\n         * Handle messages received from the shared worker and fires an\n         * event according to the message type.\n         *\n         * @param {MessageEvent} messageEv\n         * @param {{type: WorkerEvent, data: any}[]}  messageEv.data\n         */\n        function handleMessage(messageEv) {\n            const { type, data } = messageEv.data;\n            switch (type) {\n                case \"BUS:PROVIDE_LOGS\": {\n                    const blob = new Blob([JSON.stringify(data, null, 2)], {\n                        type: \"application/json\",\n                    });\n                    const url = URL.createObjectURL(blob);\n                    const a = document.createElement(\"a\");\n                    a.href = url;\n                    a.download = `bus_logs_${luxon.DateTime.now().toFormat(\n                        \"yyyy-LL-dd-HH-mm-ss\"\n                    )}.json`;\n                    a.click();\n                    URL.revokeObjectURL(url);\n                    break;\n                }\n                case \"BUS:NOTIFICATION\": {\n                    const notifications = data.map(({ id, message }) => ({ id, ...message }));\n                    state.lastNotificationId = notifications.at(-1).id;\n                    legacyMultiTab.setSharedValue(\"last_notification_id\", state.lastNotificationId);\n                    for (const { id, type, payload } of notifications) {\n                        notificationBus.trigger(type, { id, payload });\n                        busService._onMessage(env, id, type, payload);\n                    }\n                    break;\n                }\n                case \"BUS:INITIALIZED\": {\n                    connectionInitializedDeferred.resolve();\n                    break;\n                }\n                case \"BUS:WORKER_STATE_UPDATED\":\n                    state.workerState = data;\n                    break;\n                case \"BUS:OUTDATED\": {\n                    multiTab.unregister();\n                    notification.add(\n                        _t(\n                            \"Save your work and refresh to get the latest updates and avoid potential issues.\"\n                        ),\n                        {\n                            title: _t(\"The page is out of date\"),\n                            type: \"warning\",\n                            sticky: true,\n                            buttons: [\n                                {\n                                    name: _t(\"Refresh\"),\n                                    primary: true,\n                                    onClick: () => {\n                                        browser.location.reload();\n                                    },\n                                },\n                            ],\n                        }\n                    );\n                    break;\n                }\n            }\n            if (!INTERNAL_EVENTS.has(type)) {\n                bus.trigger(type, data);\n            }\n        }\n\n        /**\n         * Start the \"bus_service\" workerService.\n         */\n        async function ensureWorkerStarted() {\n            if (!connectionInitializedDeferred) {\n                connectionInitializedDeferred = new Deferred();\n                let uid = Array.isArray(session.user_id) ? session.user_id[0] : user.userId;\n                if (!uid && uid !== undefined) {\n                    uid = false;\n                }\n                await workerService.ensureWorkerStarted();\n                await workerService.registerHandler(handleMessage);\n                workerService.send(\"BUS:INITIALIZE_CONNECTION\", {\n                    websocketURL: `${params.serverURL.replace(\"http\", \"ws\")}/websocket?version=${\n                        session.websocket_worker_version\n                    }`,\n                    db: session.db,\n                    debug: odoo.debug,\n                    lastNotificationId: legacyMultiTab.getSharedValue(\"last_notification_id\", 0),\n                    uid,\n                    startTs: startedAt.valueOf(),\n                });\n            }\n            await connectionInitializedDeferred;\n        }\n\n        browser.addEventListener(\"pagehide\", ({ persisted }) => {\n            if (!persisted) {\n                // Page is gonna be unloaded, disconnect this client\n                // from the worker.\n                workerService.send(\"BUS:LEAVE\");\n            }\n        });\n        browser.addEventListener(\n            \"online\",\n            () => {\n                backOnlineTimeout = browser.setTimeout(() => {\n                    if (state.isActive) {\n                        workerService.send(\"BUS:START\");\n                    }\n                }, BACK_ONLINE_RECONNECT_DELAY);\n            },\n            { capture: true }\n        );\n        browser.addEventListener(\n            \"offline\",\n            () => {\n                clearTimeout(backOnlineTimeout);\n                workerService.send(\"BUS:STOP\");\n            },\n            {\n                capture: true,\n            }\n        );\n        const state = reactive({\n            addEventListener: bus.addEventListener.bind(bus),\n            addChannel: async (channel) => {\n                await ensureWorkerStarted();\n                workerService.send(\"BUS:ADD_CHANNEL\", channel);\n                workerService.send(\"BUS:START\");\n                state.isActive = true;\n            },\n            deleteChannel: (channel) => {\n                workerService.send(\"BUS:DELETE_CHANNEL\", channel);\n            },\n            setLoggingEnabled: (isEnabled) =>\n                workerService.send(\"BUS:SET_LOGGING_ENABLED\", isEnabled),\n            downloadLogs: () => workerService.send(\"BUS:REQUEST_LOGS\"),\n            forceUpdateChannels: () => workerService.send(\"BUS:FORCE_UPDATE_CHANNELS\"),\n            trigger: bus.trigger.bind(bus),\n            removeEventListener: bus.removeEventListener.bind(bus),\n            send: (eventName, data) =>\n                workerService.send(\"BUS:SEND\", { event_name: eventName, data }),\n            start: async () => {\n                await ensureWorkerStarted();\n                workerService.send(\"BUS:START\");\n                state.isActive = true;\n            },\n            stop: () => {\n                workerService.send(\"BUS:LEAVE\");\n                state.isActive = false;\n            },\n            isActive: false,\n            /**\n             * Subscribe to a single notification type.\n             *\n             * @param {string} notificationType\n             * @param {function} callback\n             */\n            subscribe(notificationType, callback) {\n                const wrapper = ({ detail }) => {\n                    const { id, payload } = detail;\n                    callback(JSON.parse(JSON.stringify(payload)), { id });\n                };\n                subscribeFnToWrapper.set(callback, wrapper);\n                notificationBus.addEventListener(notificationType, wrapper);\n            },\n            /**\n             * Unsubscribe from a single notification type.\n             *\n             * @param {string} notificationType\n             * @param {function} callback\n             */\n            unsubscribe(notificationType, callback) {\n                notificationBus.removeEventListener(\n                    notificationType,\n                    subscribeFnToWrapper.get(callback)\n                );\n                subscribeFnToWrapper.delete(callback);\n            },\n            startedAt,\n            workerState: null,\n            /** The id of the last notification received by this tab. */\n            lastNotificationId: null,\n        });\n        return state;\n    },\n    /** Overriden to provide logs in tests. Use subscribe() in production. */\n    _onMessage(env, id, type, payload) {},\n};\nregistry.category(\"services\").add(\"bus_service\", busService);\n", "import { EventBus } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\n\nexport const presenceService = {\n    start(env) {\n        const LOCAL_STORAGE_PREFIX = \"presence\";\n        const bus = new EventBus();\n        let isOdooFocused = true;\n        let lastPresenceTime =\n            browser.localStorage.getItem(`${LOCAL_STORAGE_PREFIX}.lastPresence`) ||\n            luxon.DateTime.now().ts;\n\n        function onPresence() {\n            lastPresenceTime = luxon.DateTime.now().ts;\n            browser.localStorage.setItem(`${LOCAL_STORAGE_PREFIX}.lastPresence`, lastPresenceTime);\n            bus.trigger(\"presence\");\n        }\n\n        function onFocusChange(isFocused) {\n            try {\n                isFocused = parent.document.hasFocus();\n            } catch {\n                // noop\n            }\n            isOdooFocused = isFocused;\n            browser.localStorage.setItem(`${LOCAL_STORAGE_PREFIX}.focus`, isOdooFocused);\n            if (isOdooFocused) {\n                lastPresenceTime = luxon.DateTime.now().ts;\n                env.bus.trigger(\"window_focus\", isOdooFocused);\n            }\n        }\n\n        function onStorage({ key, newValue }) {\n            if (key === `${LOCAL_STORAGE_PREFIX}.focus`) {\n                isOdooFocused = JSON.parse(newValue);\n                env.bus.trigger(\"window_focus\", newValue);\n            }\n            if (key === `${LOCAL_STORAGE_PREFIX}.lastPresence`) {\n                lastPresenceTime = JSON.parse(newValue);\n                bus.trigger(\"presence\");\n            }\n        }\n        browser.addEventListener(\"storage\", onStorage);\n        browser.addEventListener(\"focus\", () => onFocusChange(true));\n        browser.addEventListener(\"blur\", () => onFocusChange(false));\n        browser.addEventListener(\"pagehide\", () => onFocusChange(false));\n        browser.addEventListener(\"click\", onPresence);\n        browser.addEventListener(\"keydown\", onPresence);\n\n        return {\n            bus,\n            getLastPresence() {\n                return lastPresenceTime;\n            },\n            isOdooFocused() {\n                return isOdooFocused;\n            },\n            getInactivityPeriod() {\n                return luxon.DateTime.now().ts - this.getLastPresence();\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"presence\", presenceService);\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { session } from \"@web/session\";\n\nexport const WORKER_STATE = Object.freeze({\n    UNINITIALIZED: \"UNINITIALIZED\",\n    INITIALIZING: \"INITIALIZING\",\n    INITIALIZED: \"INITIALIZED\",\n    FAILED: \"FAILED\",\n});\n\nexport class WorkerService {\n    constructor(env, services) {\n        this.params = services[\"bus.parameters\"];\n        this.worker = null;\n        this.isUsingSharedWorker = Boolean(browser.SharedWorker);\n        this._state = WORKER_STATE.UNINITIALIZED;\n        this.connectionInitializedDeferred = new Deferred();\n    }\n\n    startWorker() {\n        this._state = WORKER_STATE.INITIALIZING;\n        let workerURL = `${this.params.serverURL}/bus/websocket_worker_bundle?v=${session.websocket_worker_version}`;\n        if (this.params.serverURL !== window.origin) {\n            // Worker service can be loaded from a different origin than the\n            // bundle URL. The Worker expects an URL from this origin, give\n            // it a base64 URL that will then load the bundle via \"importScripts\"\n            // which allows cross origin.\n            const source = `importScripts(\"${workerURL}\");`;\n            workerURL = \"data:application/javascript;base64,\" + window.btoa(source);\n        }\n        const workerClass = this.isUsingSharedWorker ? browser.SharedWorker : browser.Worker;\n        this.worker = new workerClass(workerURL, {\n            name: this.isUsingSharedWorker ? \"odoo:bus_shared_worker\" : \"odoo:bus_worker\",\n        });\n        this.worker.onerror = (e) => this.onInitError(e);\n        this._registerHandler((ev) => {\n            if (ev.data.type === \"BASE:INITIALIZED\") {\n                this._state = WORKER_STATE.INITIALIZED;\n                this.connectionInitializedDeferred.resolve();\n            }\n        });\n        if (this.isUsingSharedWorker) {\n            this.worker.port.start();\n        }\n        this._send(\"BASE:INIT\");\n    }\n\n    async ensureWorkerStarted() {\n        if (this._state === WORKER_STATE.UNINITIALIZED) {\n            this.startWorker();\n        }\n        await this.connectionInitializedDeferred;\n    }\n\n    onInitError(e) {\n        // FIXME: SharedWorker can still fail for unknown reasons even when it is supported.\n        if (this._state === WORKER_STATE.INITIALIZING && this.isUsingSharedWorker) {\n            console.warn(\"Error while loading SharedWorker, fallback on Worker: \", e);\n            this.isUsingSharedWorker = false;\n            this.worker?.port?.close?.();\n            this.startWorker();\n        } else if (this._state === WORKER_STATE.INITIALIZING) {\n            this._state = WORKER_STATE.FAILED;\n            this.connectionInitializedDeferred.resolve();\n            console.warn(\"Worker service failed to initialize: \", e);\n        }\n    }\n\n    _registerHandler(handler) {\n        if (this.isUsingSharedWorker) {\n            this.worker.port.addEventListener(\"message\", handler);\n        } else {\n            this.worker.addEventListener(\"message\", handler);\n        }\n    }\n\n    _send(action, data) {\n        const message = { action, data };\n        if (this.isUsingSharedWorker) {\n            this.worker.port.postMessage(message);\n        } else {\n            this.worker.postMessage(message);\n        }\n    }\n\n    /**\n     * Send a message to the worker. If the worker is not yet started,\n     * ignore the message. One should call `ensureWorkerStarted` if one\n     * really needs the message to reach the worker.\n     *\n     * @param {String} action Action to be executed by the worker.\n     * @param {Object|undefined} data Data required for the action to be\n     * executed.\n     */\n    async send(action, data) {\n        if (this._state === WORKER_STATE.UNINITIALIZED) {\n            return;\n        }\n        await this.connectionInitializedDeferred;\n        if (this._state === WORKER_STATE.FAILED) {\n            console.warn(\"Worker service failed to initialize, cannot send message.\");\n        }\n        this._send(action, data);\n    }\n\n    /**\n     * Register a function to handle messages from the worker.\n     *\n     * @param {function} handler\n     */\n    async registerHandler(handler) {\n        if (this._state === WORKER_STATE.UNINITIALIZED) {\n            this.startWorker();\n        }\n        await this.connectionInitializedDeferred;\n        if (this._state === WORKER_STATE.FAILED) {\n            console.warn(\"Worker service failed to initialize, cannot register handler.\");\n        }\n        this._registerHandler(handler);\n    }\n\n    get state() {\n        return this._state;\n    }\n}\n\nexport const workerService = {\n    dependencies: [\"bus.parameters\"],\n    start(env, services) {\n        return new WorkerService(env, services);\n    },\n};\n\nregistry.category(\"services\").add(\"worker_service\", workerService);\n", "export class BaseWorker {\n    constructor(name) {\n        this.name = name;\n        this.client = null; // only for testing purposes\n    }\n\n    handleMessage(event) {\n        const { action } = event.data;\n        if (action === \"BASE:INIT\") {\n            if (this.name.includes(\"shared\")) {\n                event.target.postMessage({ type: \"BASE:INITIALIZED\" });\n            } else {\n                (this.client || globalThis).postMessage({ type: \"BASE:INITIALIZED\" });\n            }\n        }\n    }\n}\n", "/**\n * Returns a function, that, as long as it continues to be invoked, will not\n * be triggered. The function will be called after it stops being called for\n * N milliseconds. If `immediate` is passed, trigger the function on the\n * leading edge, instead of the trailing.\n *\n * Inspired by https://davidwalsh.name/javascript-debounce-function\n */\nexport function debounce(func, wait, immediate) {\n    let timeout;\n    return function () {\n        const context = this;\n        const args = arguments;\n        function later() {\n            timeout = null;\n            if (!immediate) {\n                func.apply(context, args);\n            }\n        }\n        const callNow = immediate && !timeout;\n        clearTimeout(timeout);\n        timeout = setTimeout(later, wait);\n        if (callNow) {\n            func.apply(context, args);\n        }\n    };\n}\n\n/**\n * Deferred is basically a resolvable/rejectable extension of Promise.\n */\nexport class Deferred extends Promise {\n    constructor() {\n        let resolve;\n        let reject;\n        const prom = new Promise((res, rej) => {\n            resolve = res;\n            reject = rej;\n        });\n        return Object.assign(prom, { resolve, reject });\n    }\n}\n\nexport class Logger {\n    static LOG_TTL = 24 * 60 * 60 * 1000; // 24 hours\n    static gcInterval = null;\n    static instances = [];\n    _db;\n\n    static async gcOutdatedLogs() {\n        const threshold = Date.now() - Logger.LOG_TTL;\n        for (const logger of this.instances) {\n            try {\n                await logger._ensureDatabaseAvailable();\n                await new Promise((res, rej) => {\n                    const transaction = logger._db.transaction(\"logs\", \"readwrite\");\n                    const store = transaction.objectStore(\"logs\");\n                    const req = store\n                        .index(\"timestamp\")\n                        .openCursor(IDBKeyRange.upperBound(threshold));\n                    req.onsuccess = (event) => {\n                        const cursor = event.target.result;\n                        if (cursor) {\n                            cursor.delete();\n                            cursor.continue();\n                        }\n                    };\n                    req.onerror = (e) => rej(e.target.error);\n                    transaction.oncomplete = res;\n                    transaction.onerror = (e) => rej(e.target.error);\n                });\n            } catch (error) {\n                console.error(`Failed to clear logs for logger \"${logger._name}\":`, error);\n            }\n        }\n    }\n\n    constructor(name) {\n        this._name = name;\n        Logger.instances.push(this);\n        Logger.gcOutdatedLogs();\n        clearInterval(Logger.gcInterval);\n        Logger.gcInterval = setInterval(() => Logger.gcOutdatedLogs(), Logger.LOG_TTL);\n    }\n\n    async _ensureDatabaseAvailable() {\n        if (this._db) {\n            return;\n        }\n        return new Promise((res, rej) => {\n            const request = indexedDB.open(this._name, 1);\n            request.onsuccess = (event) => {\n                this._db = event.target.result;\n                res();\n            };\n            request.onupgradeneeded = (event) => {\n                if (!event.target.result.objectStoreNames.contains(\"logs\")) {\n                    const store = event.target.result.createObjectStore(\"logs\", {\n                        autoIncrement: true,\n                    });\n                    store.createIndex(\"timestamp\", \"timestamp\", { unique: false });\n                }\n            };\n            request.onerror = rej;\n        });\n    }\n\n    async log(message) {\n        await this._ensureDatabaseAvailable();\n        const transaction = this._db.transaction(\"logs\", \"readwrite\");\n        const store = transaction.objectStore(\"logs\");\n        const addRequest = store.add({ timestamp: Date.now(), message });\n        return new Promise((res, rej) => {\n            addRequest.onsuccess = res;\n            addRequest.onerror = rej;\n        });\n    }\n\n    async getLogs() {\n        await Logger.gcOutdatedLogs();\n        await this._ensureDatabaseAvailable();\n        const transaction = this._db.transaction(\"logs\", \"readonly\");\n        const store = transaction.objectStore(\"logs\");\n        const request = store.getAll();\n        return new Promise((res, rej) => {\n            request.onsuccess = (ev) => res(ev.target.result.map(({ message }) => message));\n            request.onerror = rej;\n        });\n    }\n}\n", "import { Deferred } from \"@bus/workers/bus_worker_utils\";\n\nexport class ElectionWorker {\n    MAIN_TAB_TIMEOUT_PERIOD = 3000;\n\n    /** @type {Set<MessagePort>} */\n    candidates = new Set();\n    /** @type {Deferred|null} */\n    electionDeferred = null;\n    /** @type {number|null} */\n    heartbeatRequestInterval = null;\n    lastHeartbeat = Date.now();\n    /** @type {Deferred|null} */\n    masterReplyDeferred = null;\n    /** @type {MessagePort|null} */\n    masterTab = null;\n\n    constructor() {\n        setInterval(() => {\n            if (Date.now() - this.lastHeartbeat > this.MAIN_TAB_TIMEOUT_PERIOD) {\n                this.startElection();\n            }\n        }, this.MAIN_TAB_TIMEOUT_PERIOD);\n    }\n\n    requestHeartbeat(messagePort) {\n        if (messagePort) {\n            messagePort.postMessage({ type: \"ELECTION:HEARTBEAT_REQUEST\" });\n            return;\n        }\n        for (const candidate of this.candidates) {\n            candidate.postMessage({ type: \"ELECTION:HEARTBEAT_REQUEST\" });\n        }\n    }\n\n    async ensureMasterPresence() {\n        this.masterReplyDeferred ??= new Deferred();\n        if (this.masterTab) {\n            this.requestHeartbeat(this.masterTab);\n        } else {\n            this.startElection();\n        }\n        await this.masterReplyDeferred;\n    }\n\n    startElection() {\n        clearInterval(this.heartbeatRequestInterval);\n        this.masterTab?.postMessage({ type: \"ELECTION:UNASSIGN_MASTER\" });\n        this.masterTab = null;\n        this.electionDeferred ??= new Deferred();\n        this.requestHeartbeat();\n    }\n\n    finishElection(messagePort) {\n        this.masterTab = messagePort;\n        messagePort.postMessage({ type: \"ELECTION:ASSIGN_MASTER\" });\n        this.electionDeferred.resolve();\n        this.electionDeferred = null;\n        this.heartbeatRequestInterval = setInterval(\n            () => this.requestHeartbeat(this.masterTab),\n            this.MAIN_TAB_TIMEOUT_PERIOD / 2\n        );\n    }\n\n    async handleMessage(event) {\n        const { action } = event.data;\n        if (!action?.startsWith(\"ELECTION:\")) {\n            return;\n        }\n        switch (action) {\n            case \"ELECTION:REGISTER\":\n                this.candidates.add(event.target);\n                await this.electionDeferred;\n                if (!this.masterTab) {\n                    this.startElection();\n                }\n                break;\n            case \"ELECTION:UNREGISTER\":\n                this.candidates.delete(event.target);\n                if (this.masterTab === event.target) {\n                    this.startElection();\n                }\n                break;\n            case \"ELECTION:IS_MASTER?\":\n                await this.ensureMasterPresence();\n                event.target.postMessage({\n                    type: \"ELECTION:IS_MASTER_RESPONSE\",\n                    data: { answer: this.masterTab === event.target },\n                });\n                break;\n            case \"ELECTION:HEARTBEAT\":\n                if (this.electionDeferred) {\n                    this.finishElection(event.target);\n                }\n                if (this.masterTab === event.target) {\n                    this.lastHeartbeat = Date.now();\n                    this.masterReplyDeferred?.resolve();\n                    this.masterReplyDeferred = null;\n                }\n                break;\n            default:\n                console.warn(\"Unknown message action:\", action);\n        }\n    }\n}\n", "import { debounce, Deferred, Logger } from \"@bus/workers/bus_worker_utils\";\n\n/**\n * Type of events that can be sent from the worker to its clients.\n *\n * @typedef { 'BUS:CONNECT' | 'BUS:RECONNECT' | 'BUS:DISCONNECT' | 'BUS:RECONNECTING' | 'BUS:NOTIFICATION' | 'BUS:INITIALIZED' | 'BUS:OUTDATED'| 'BUS:WORKER_STATE_UPDATED' | 'BUS:PROVIDE_LOGS' } WorkerEvent\n */\n\n/**\n * Type of action that can be sent from the client to the worker.\n *\n * @typedef {'BUS:ADD_CHANNEL' | 'BUS:DELETE_CHANNEL' | 'BUS:FORCE_UPDATE_CHANNELS' | 'BUS:INITIALIZE_CONNECTION' | 'BUS:REQUEST_LOGS' | 'BUS:SEND' | 'BUS:SET_LOGGING_ENABLED' | 'BUS:LEAVE' | 'BUS:STOP' | 'BUS:START'} WorkerAction\n */\n\nexport const WEBSOCKET_CLOSE_CODES = Object.freeze({\n    CLEAN: 1000,\n    GOING_AWAY: 1001,\n    PROTOCOL_ERROR: 1002,\n    INCORRECT_DATA: 1003,\n    ABNORMAL_CLOSURE: 1006,\n    INCONSISTENT_DATA: 1007,\n    MESSAGE_VIOLATING_POLICY: 1008,\n    MESSAGE_TOO_BIG: 1009,\n    EXTENSION_NEGOTIATION_FAILED: 1010,\n    SERVER_ERROR: 1011,\n    RESTART: 1012,\n    TRY_LATER: 1013,\n    BAD_GATEWAY: 1014,\n    SESSION_EXPIRED: 4001,\n    KEEP_ALIVE_TIMEOUT: 4002,\n    RECONNECTING: 4003,\n    CLOSING_HANDSHAKE_ABORTED: 4004,\n});\nexport const WORKER_STATE = Object.freeze({\n    CONNECTED: \"CONNECTED\",\n    DISCONNECTED: \"DISCONNECTED\",\n    IDLE: \"IDLE\",\n    CONNECTING: \"CONNECTING\",\n});\nconst MAXIMUM_RECONNECT_DELAY = 60000;\nconst UUID = Date.now().toString(36) + Math.random().toString(36).substring(2);\nconst logger = new Logger(\"bus_websocket_worker\");\n\n/**\n * This class regroups the logic necessary in order for the\n * SharedWorker/Worker to work. Indeed, Safari and some minor browsers\n * do not support SharedWorker. In order to solve this issue, a Worker\n * is used in this case. The logic is almost the same than the one used\n * for SharedWorker and this class implements it.\n */\nexport class WebsocketWorker {\n    INITIAL_RECONNECT_DELAY = 1000;\n    RECONNECT_JITTER = 1000;\n    CONNECTION_CHECK_DELAY = 60_000;\n\n    constructor(name) {\n        this.name = name;\n        // Timestamp of start of most recent bus service sender\n        this.newestStartTs = undefined;\n        this.websocketURL = \"\";\n        this.currentUID = null;\n        this.currentDB = null;\n        this.isWaitingForNewUID = true;\n        this.channelsByClient = new Map();\n        this.connectRetryDelay = this.INITIAL_RECONNECT_DELAY;\n        this.connectTimeout = null;\n        this.debugModeByClient = new Map();\n        this.isDebug = false;\n        this.active = true;\n        this.state = WORKER_STATE.IDLE;\n        this.isReconnecting = false;\n        this.lastChannelSubscription = null;\n        this.loggingEnabled = null;\n        this.firstSubscribeDeferred = new Deferred();\n        this.lastNotificationId = 0;\n        this.messageWaitQueue = [];\n        this._forceUpdateChannels = debounce(this._forceUpdateChannels, 300);\n        this._debouncedUpdateChannels = debounce(this._updateChannels, 300);\n        this._debouncedSendToServer = debounce(this._sendToServer, 300);\n\n        this._onWebsocketClose = this._onWebsocketClose.bind(this);\n        this._onWebsocketError = this._onWebsocketError.bind(this);\n        this._onWebsocketMessage = this._onWebsocketMessage.bind(this);\n        this._onWebsocketOpen = this._onWebsocketOpen.bind(this);\n\n        globalThis.addEventListener(\"error\", ({ error }) => {\n            const params = error instanceof Error ? [error.constructor.name, error.stack] : [error];\n            this._logDebug(\"Unhandled error\", ...params);\n        });\n        globalThis.addEventListener(\"unhandledrejection\", ({ reason }) => {\n            const params =\n                reason instanceof Error ? [reason.constructor.name, reason.stack] : [reason];\n            this._logDebug(\"Unhandled rejection\", params);\n        });\n    }\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Send the message to all the clients that are connected to the\n     * worker.\n     *\n     * @param {WorkerEvent} type Event to broadcast to connected\n     * clients.\n     * @param {Object} data\n     */\n    broadcast(type, data) {\n        this._logDebug(\"broadcast\", type, data);\n        for (const client of this.channelsByClient.keys()) {\n            client.postMessage({ type, data: data ? JSON.parse(JSON.stringify(data)) : undefined });\n        }\n    }\n\n    /**\n     * Register a client handled by this worker.\n     *\n     * @param {MessagePort} messagePort\n     */\n    registerClient(messagePort) {\n        messagePort.addEventListener(\"message\", (ev) => {\n            this._onClientMessage(messagePort, ev.data);\n        });\n        this.channelsByClient.set(messagePort, []);\n    }\n\n    /**\n     * Send message to the given client.\n     *\n     * @param {number} client\n     * @param {WorkerEvent} type\n     * @param {Object} data\n     */\n    sendToClient(client, type, data) {\n        if (type !== \"BUS:PROVIDE_LOGS\") {\n            this._logDebug(\"sendToClient\", type, data);\n        }\n        client.postMessage({ type, data: data ? JSON.parse(JSON.stringify(data)) : undefined });\n    }\n\n    //--------------------------------------------------------------------------\n    // PRIVATE\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when a message is posted to the worker by a client (i.e. a\n     * MessagePort connected to this worker).\n     *\n     * @param {MessagePort} client\n     * @param {Object} message\n     * @param {WorkerAction} [message.action]\n     * Action to execute.\n     * @param {Object|undefined} [message.data] Data required by the\n     * action.\n     */\n    _onClientMessage(client, { action, data }) {\n        this._logDebug(\"_onClientMessage\", action, data);\n        switch (action) {\n            case \"BUS:SEND\": {\n                if (data[\"event_name\"] === \"update_presence\") {\n                    this._debouncedSendToServer(data);\n                } else {\n                    this._sendToServer(data);\n                }\n                return;\n            }\n            case \"BUS:START\":\n                return this._start();\n            case \"BUS:STOP\":\n                return this._stop();\n            case \"BUS:LEAVE\":\n                return this._unregisterClient(client);\n            case \"BUS:ADD_CHANNEL\":\n                return this._addChannel(client, data);\n            case \"BUS:DELETE_CHANNEL\":\n                return this._deleteChannel(client, data);\n            case \"BUS:FORCE_UPDATE_CHANNELS\":\n                return this._forceUpdateChannels();\n            case \"BUS:SET_LOGGING_ENABLED\":\n                this.loggingEnabled = data;\n                break;\n            case \"BUS:REQUEST_LOGS\":\n                logger.getLogs().then((logs) => {\n                    const workerInfo = {\n                        UUID,\n                        active: this.active,\n                        channels: [\n                            ...new Set([].concat.apply([], [...this.channelsByClient.values()])),\n                        ].sort(),\n                        db: this.currentDB,\n                        is_reconnecting: this.isReconnecting,\n                        last_subscription: this.lastChannelSubscription,\n                        name: this.name,\n                        number_of_clients: this.channelsByClient.size,\n                        reconnect_delay: this.connectRetryDelay,\n                        uid: this.currentUID,\n                        websocket_url: this.websocketURL,\n                    };\n                    this.sendToClient(client, \"BUS:PROVIDE_LOGS\", { workerInfo, logs });\n                });\n                break;\n            case \"BUS:INITIALIZE_CONNECTION\":\n                return this._initializeConnection(client, data);\n        }\n    }\n\n    /**\n     * Add a channel for the given client. If this channel is not yet\n     * known, update the subscription on the server.\n     *\n     * @param {MessagePort} client\n     * @param {string} channel\n     */\n    _addChannel(client, channel) {\n        this.channelsByClient.get(client).push(channel);\n        this._debouncedUpdateChannels();\n    }\n\n    /**\n     * Remove a channel for the given client. If this channel is not\n     * used anymore, update the subscription on the server.\n     *\n     * @param {MessagePort} client\n     * @param {string} channel\n     */\n    _deleteChannel(client, channel) {\n        const clientChannels = this.channelsByClient.get(client);\n        if (!clientChannels) {\n            return;\n        }\n        const channelIndex = clientChannels.indexOf(channel);\n        if (channelIndex !== -1) {\n            clientChannels.splice(channelIndex, 1);\n            this._debouncedUpdateChannels();\n        }\n    }\n\n    /**\n     * Update the channels on the server side even if the channels on\n     * the client side are the same than the last time we subscribed.\n     */\n    _forceUpdateChannels() {\n        this._updateChannels({ force: true });\n    }\n\n    /**\n     * Remove the given client from this worker client list as well as\n     * its channels. If some of its channels are not used anymore,\n     * update the subscription on the server.\n     *\n     * @param {MessagePort} client\n     */\n    _unregisterClient(client) {\n        this.channelsByClient.delete(client);\n        this.debugModeByClient.delete(client);\n        this.isDebug = [...this.debugModeByClient.values()].some(Boolean);\n        this._debouncedUpdateChannels();\n    }\n\n    /**\n     * Initialize a client connection to this worker.\n     *\n     * @param {Object} param0\n     * @param {string} [param0.db] Database name.\n     * @param {String} [param0.debug] Current debugging mode for the\n     * given client.\n     * @param {Number} [param0.lastNotificationId] Last notification id\n     * known by the client.\n     * @param {String} [param0.websocketURL] URL of the websocket endpoint.\n     * @param {Number|false|undefined} [param0.uid] Current user id\n     *     - Number: user is logged whether on the frontend/backend.\n     *     - false: user is not logged.\n     *     - undefined: not available (e.g. livechat support page)\n     * @param {Number} param0.startTs Timestamp of start of bus service sender.\n     */\n    _initializeConnection(client, { db, debug, lastNotificationId, uid, websocketURL, startTs }) {\n        if (this.newestStartTs && this.newestStartTs > startTs) {\n            this.debugModeByClient.set(client, debug);\n            this.isDebug = [...this.debugModeByClient.values()].some(Boolean);\n            this.sendToClient(client, \"BUS:WORKER_STATE_UPDATED\", this.state);\n            this.sendToClient(client, \"BUS:INITIALIZED\");\n            return;\n        }\n        this.newestStartTs = startTs;\n        this.websocketURL = websocketURL;\n        this.lastNotificationId = lastNotificationId;\n        this.debugModeByClient.set(client, debug);\n        this.isDebug = [...this.debugModeByClient.values()].some(Boolean);\n        const isCurrentUserKnown = uid !== undefined;\n        if (this.isWaitingForNewUID && isCurrentUserKnown) {\n            this.isWaitingForNewUID = false;\n            this.currentUID = uid;\n        }\n        this.currentDB ||= db;\n        if ((this.currentUID !== uid && isCurrentUserKnown) || (db && this.currentDB !== db)) {\n            this.currentUID = uid;\n            this.currentDB = db || this.currentDB;\n            if (this.websocket) {\n                this.websocket.close(WEBSOCKET_CLOSE_CODES.CLEAN);\n            }\n            this.channelsByClient.forEach((_, key) => this.channelsByClient.set(key, []));\n        }\n        this.sendToClient(client, \"BUS:WORKER_STATE_UPDATED\", this.state);\n        this.sendToClient(client, \"BUS:INITIALIZED\");\n        if (!this.active) {\n            this.sendToClient(client, \"BUS:OUTDATED\");\n        }\n    }\n\n    /**\n     * Determine whether or not the websocket associated to this worker\n     * is connected.\n     *\n     * @returns {boolean}\n     */\n    _isWebsocketConnected() {\n        return this.websocket && this.websocket.readyState === 1;\n    }\n\n    /**\n     * Determine whether or not the websocket associated to this worker\n     * is connecting.\n     *\n     * @returns {boolean}\n     */\n    _isWebsocketConnecting() {\n        return this.websocket && this.websocket.readyState === 0;\n    }\n\n    /**\n     * Determine whether or not the websocket associated to this worker\n     * is in the closing state.\n     *\n     * @returns {boolean}\n     */\n    _isWebsocketClosing() {\n        return this.websocket && this.websocket.readyState === 2;\n    }\n\n    /**\n     * Triggered when a connection is closed. If closure was not clean ,\n     * try to reconnect after indicating to the clients that the\n     * connection was closed.\n     *\n     * @param {CloseEvent} ev\n     * @param {number} code  close code indicating why the connection\n     * was closed.\n     * @param {string} reason reason indicating why the connection was\n     * closed.\n     */\n    _onWebsocketClose({ code, reason }) {\n        clearInterval(this._connectionCheckInterval);\n        this._logDebug(\"_onWebsocketClose\", code, reason);\n        this._updateState(WORKER_STATE.DISCONNECTED);\n        this.lastChannelSubscription = null;\n        this.firstSubscribeDeferred = new Deferred();\n        if (this.isReconnecting) {\n            // Connection was not established but the close event was\n            // triggered anyway. Let the onWebsocketError method handle\n            // this case.\n            return;\n        }\n        this.broadcast(\"BUS:DISCONNECT\", { code, reason });\n        if (code === WEBSOCKET_CLOSE_CODES.CLEAN) {\n            if (reason === \"OUTDATED_VERSION\") {\n                console.warn(\"Worker deactivated due to an outdated version.\");\n                this.active = false;\n                this.broadcast(\"BUS:OUTDATED\");\n            }\n            // WebSocket was closed on purpose, do not try to reconnect.\n            return;\n        }\n        // WebSocket was not closed cleanly, let's try to reconnect.\n        this.broadcast(\"BUS:RECONNECTING\", { closeCode: code });\n        this.isReconnecting = true;\n        if (\n            [\n                WEBSOCKET_CLOSE_CODES.KEEP_ALIVE_TIMEOUT,\n                WEBSOCKET_CLOSE_CODES.CLOSING_HANDSHAKE_ABORTED,\n            ].includes(code)\n        ) {\n            // Don't wait to reconnect: keep-alive shouldn't be noticed, and the\n            // closing handshake was aborted because the client explicitly tried\n            // to connect while the socket was stuck in the closing state.\n            this.connectRetryDelay = 0;\n        }\n        if (code === WEBSOCKET_CLOSE_CODES.SESSION_EXPIRED) {\n            this.isWaitingForNewUID = true;\n        }\n        this._retryConnectionWithDelay();\n    }\n\n    /**\n     * Triggered when a connection failed or failed to established.\n     */\n    _onWebsocketError() {\n        this._logDebug(\"_onWebsocketError\");\n        this._retryConnectionWithDelay();\n    }\n\n    /**\n     * Handle data received from the bus.\n     *\n     * @param {MessageEvent} messageEv\n     */\n    _onWebsocketMessage(messageEv) {\n        this._restartConnectionCheckInterval();\n        const notifications = JSON.parse(messageEv.data);\n        this._logDebug(\"_onWebsocketMessage\", notifications);\n        this.lastNotificationId = notifications[notifications.length - 1].id;\n        this.broadcast(\"BUS:NOTIFICATION\", notifications);\n    }\n\n    async _logDebug(title, ...args) {\n        if (this.loggingEnabled) {\n            try {\n                await logger.log({\n                    dt: new Date().toISOString(),\n                    event: title,\n                    args,\n                    worker: UUID,\n                });\n            } catch (e) {\n                console.error(e);\n            }\n        }\n    }\n\n    /**\n     * Triggered on websocket open. Send message that were waiting for\n     * the connection to open.\n     */\n    _onWebsocketOpen() {\n        this._logDebug(\"_onWebsocketOpen\");\n        this._updateState(WORKER_STATE.CONNECTED);\n        this.broadcast(this.isReconnecting ? \"BUS:RECONNECT\" : \"BUS:CONNECT\");\n        this._debouncedUpdateChannels();\n        this.connectRetryDelay = this.INITIAL_RECONNECT_DELAY;\n        this.connectTimeout = null;\n        this.isReconnecting = false;\n        this.firstSubscribeDeferred.then(() => {\n            if (!this.websocket) {\n                return;\n            }\n            this.messageWaitQueue.forEach((msg) => this.websocket.send(msg));\n            this.messageWaitQueue = [];\n        });\n        this._restartConnectionCheckInterval();\n    }\n\n    /**\n     * Sends a custom application-level message to perform a connection check\n     * on the WebSocket.\n     *\n     * Browsers rely on the OS's TCP mechanism, which can take minutes or\n     * hours to detect a dead connection. Sending data triggers an immediate\n     * I/O operation, quickly revealing any network-level failure. This must be\n     * implemented at the application level because the browser WebSocket API\n     * does not expose the built-in ping/pong mechanism.\n     */\n    _restartConnectionCheckInterval() {\n        clearInterval(this._connectionCheckInterval);\n        this._connectionCheckInterval = setInterval(() => {\n            if (this._isWebsocketConnected()) {\n                this.websocket.send(new Uint8Array([0x00]));\n                this._logDebug(\"connection_checked\");\n            }\n        }, this.CONNECTION_CHECK_DELAY);\n    }\n\n    /**\n     * Try to reconnect to the server, an exponential back off is\n     * applied to the reconnect attempts.\n     */\n    _retryConnectionWithDelay() {\n        this.connectRetryDelay =\n            Math.min(this.connectRetryDelay * 1.5, MAXIMUM_RECONNECT_DELAY) +\n            this.RECONNECT_JITTER * Math.random();\n        this._logDebug(\"_retryConnectionWithDelay\", this.connectRetryDelay);\n        this.connectTimeout = setTimeout(this._start.bind(this), this.connectRetryDelay);\n    }\n\n    /**\n     * Send a message to the server through the websocket connection.\n     * If the websocket is not open, enqueue the message and send it\n     * upon the next reconnection.\n     *\n     * @param {{event_name: string, data: any }} message Message to send to the server.\n     */\n    _sendToServer(message) {\n        this._logDebug(\"_sendToServer\", message);\n        const payload = JSON.stringify(message);\n        if (!this._isWebsocketConnected()) {\n            if (message[\"event_name\"] === \"subscribe\") {\n                this.messageWaitQueue = this.messageWaitQueue.filter(\n                    (msg) => JSON.parse(msg).event_name !== \"subscribe\"\n                );\n                this.messageWaitQueue.unshift(payload);\n            } else {\n                this.messageWaitQueue.push(payload);\n            }\n        } else {\n            if (message[\"event_name\"] === \"subscribe\") {\n                this.websocket.send(payload);\n            } else {\n                this.firstSubscribeDeferred.then(() => this.websocket.send(payload));\n            }\n            this._restartConnectionCheckInterval();\n        }\n    }\n\n    _removeWebsocketListeners() {\n        this.websocket?.removeEventListener(\"open\", this._onWebsocketOpen);\n        this.websocket?.removeEventListener(\"message\", this._onWebsocketMessage);\n        this.websocket?.removeEventListener(\"error\", this._onWebsocketError);\n        this.websocket?.removeEventListener(\"close\", this._onWebsocketClose);\n    }\n\n    /**\n     * Start the worker by opening a websocket connection.\n     */\n    _start() {\n        this._logDebug(\"_start\");\n        if (!this.active || this._isWebsocketConnected() || this._isWebsocketConnecting()) {\n            return;\n        }\n        this._removeWebsocketListeners();\n        if (this._isWebsocketClosing()) {\n            // The close event didn\u2019t trigger. Trigger manually to maintain\n            // correct state and lifecycle handling.\n            this._onWebsocketClose(\n                new CloseEvent(\"close\", { code: WEBSOCKET_CLOSE_CODES.CLOSING_HANDSHAKE_ABORTED })\n            );\n            this.websocket = null;\n            return;\n        }\n        this._updateState(WORKER_STATE.CONNECTING);\n        this.websocket = new WebSocket(this.websocketURL);\n        this.websocket.addEventListener(\"open\", this._onWebsocketOpen);\n        this.websocket.addEventListener(\"error\", this._onWebsocketError);\n        this.websocket.addEventListener(\"message\", this._onWebsocketMessage);\n        this.websocket.addEventListener(\"close\", this._onWebsocketClose);\n    }\n\n    /**\n     * Stop the worker.\n     */\n    _stop() {\n        this._logDebug(\"_stop\");\n        clearTimeout(this.connectTimeout);\n        this.connectRetryDelay = this.INITIAL_RECONNECT_DELAY;\n        this.isReconnecting = false;\n        this.lastChannelSubscription = null;\n        const shouldBroadcastClose =\n            this.websocket && this.websocket.readyState !== WebSocket.CLOSED;\n        this.websocket?.close();\n        this._removeWebsocketListeners();\n        this.websocket = null;\n        if (shouldBroadcastClose) {\n            this.broadcast(\"BUS:DISCONNECT\", { code: WEBSOCKET_CLOSE_CODES.CLEAN });\n        }\n    }\n\n    /**\n     * Update the channel subscription on the server. Ignore if the channels\n     * did not change since the last subscription.\n     *\n     * @param {boolean} force Whether or not we should update the subscription\n     * event if the channels haven't change since last subscription.\n     */\n    _updateChannels({ force = false } = {}) {\n        const allTabsChannels = [\n            ...new Set([].concat.apply([], [...this.channelsByClient.values()])),\n        ].sort();\n        const allTabsChannelsString = JSON.stringify(allTabsChannels);\n        const shouldUpdateChannelSubscription =\n            allTabsChannelsString !== this.lastChannelSubscription;\n        if (force || shouldUpdateChannelSubscription) {\n            this.lastChannelSubscription = allTabsChannelsString;\n            this._sendToServer({\n                event_name: \"subscribe\",\n                data: { channels: allTabsChannels, last: this.lastNotificationId },\n            });\n            this.firstSubscribeDeferred.resolve();\n        }\n    }\n    /**\n     * Update the worker state and broadcast the new state to its clients.\n     *\n     * @param {WORKER_STATE[keyof WORKER_STATE]} newState\n     */\n    _updateState(newState) {\n        this.state = newState;\n        this.broadcast(\"BUS:WORKER_STATE_UPDATED\", newState);\n    }\n}\n", "import { Component, useEffect, useRef } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { usePosition } from \"@web/core/position/position_hook\";\n\n/**\n * @typedef {import(\"./tour_pointer_state\").TourPointerState} TourPointerState\n *\n * @typedef TourPointerProps\n * @property {TourPointerState} pointerState\n * @property {boolean} bounce\n */\n\n/** @extends {Component<TourPointerProps, any>} */\nexport class TourPointer extends Component {\n    static props = {\n        pointerState: {\n            type: Object,\n            shape: {\n                anchor: { type: HTMLElement, optional: true },\n                content: { type: String, optional: true },\n                isOpen: { type: Boolean, optional: true },\n                isVisible: { type: Boolean, optional: true },\n                isZone: { type: Boolean, optional: true },\n                onClick: { type: [Function, { value: null }], optional: true },\n                onMouseEnter: { type: [Function, { value: null }], optional: true },\n                onMouseLeave: { type: [Function, { value: null }], optional: true },\n                position: {\n                    type: [\n                        { value: \"left\" },\n                        { value: \"right\" },\n                        { value: \"top\" },\n                        { value: \"bottom\" },\n                    ],\n                    optional: true,\n                },\n                rev: { type: Number, optional: true },\n            },\n        },\n        bounce: { type: Boolean, optional: true },\n    };\n\n    static defaultProps = {\n        bounce: true,\n    };\n\n    static template = \"web_tour.TourPointer\";\n    static width = 28; // in pixels\n    static height = 28; // in pixels\n\n    setup() {\n        this.orm = useService(\"orm\");\n        const positionOptions = {\n            margin: 6,\n            onPositioned: (pointer, position) => {\n                const popperRect = pointer.getBoundingClientRect();\n                const { top, left, direction } = position;\n                if (direction === \"top\") {\n                    // position from the bottom instead of the top as it is needed\n                    // to ensure the expand animation is properly done\n                    pointer.style.bottom = `${window.innerHeight - top - popperRect.height}px`;\n                    pointer.style.removeProperty(\"top\");\n                } else if (direction === \"left\") {\n                    // position from the right instead of the left as it is needed\n                    // to ensure the expand animation is properly done\n                    pointer.style.right = `${window.innerWidth - left - popperRect.width}px`;\n                    pointer.style.removeProperty(\"left\");\n                }\n            },\n        };\n        Object.defineProperty(positionOptions, \"position\", {\n            get: () => this.position,\n            set: () => {}, // do not let the position hook change the position\n            enumerable: true,\n        });\n        const position = usePosition(\n            \"pointer\",\n            () => this.props.pointerState.anchor,\n            positionOptions\n        );\n        const rootRef = useRef(\"pointer\");\n        const zoneRef = useRef(\"zone\");\n        /** @type {DOMREct | null} */\n        let dimensions = null;\n        let lastMeasuredContent = null;\n        let lastOpenState = this.isOpen;\n        let lastAnchor;\n        let [anchorX, anchorY] = [0, 0];\n        useEffect(() => {\n            const { el: pointer } = rootRef;\n            const { el: zone } = zoneRef;\n            if (pointer) {\n                const hasContentChanged = lastMeasuredContent !== this.content;\n                const hasOpenStateChanged = lastOpenState !== this.isOpen;\n                lastOpenState = this.isOpen;\n\n                // Check is the pointed element is a zone\n                if (this.props.pointerState.isZone) {\n                    const { anchor } = this.props.pointerState;\n                    let offsetLeft = 0;\n                    let offsetTop = 0;\n                    if (document !== anchor.ownerDocument) {\n                        const iframe = [...document.querySelectorAll(\"iframe\")].filter(\n                            (e) => e.contentDocument === anchor.ownerDocument\n                        )[0];\n                        offsetLeft = iframe.getBoundingClientRect().left;\n                        offsetTop = iframe.getBoundingClientRect().top;\n                    }\n                    const { left, top, width, height } = anchor.getBoundingClientRect();\n                    zone.style.minWidth = width + \"px\";\n                    zone.style.minHeight = height + \"px\";\n                    zone.style.left = left + offsetLeft + \"px\";\n                    zone.style.top = top + offsetTop + \"px\";\n                }\n\n                // Content changed: we must re-measure the dimensions of the text.\n                if (hasContentChanged) {\n                    lastMeasuredContent = this.content;\n                    pointer.style.removeProperty(\"width\");\n                    pointer.style.removeProperty(\"height\");\n                    dimensions = pointer.getBoundingClientRect();\n                }\n\n                // If the content or the \"is open\" state changed: we must apply\n                // new width and height properties\n                if (hasContentChanged || hasOpenStateChanged) {\n                    const [width, height] = this.isOpen\n                        ? [dimensions.width, dimensions.height]\n                        : [this.constructor.width, this.constructor.height];\n                    if (this.isOpen) {\n                        pointer.style.removeProperty(\"transition\");\n                    } else {\n                        // No transition if switching from open to closed\n                        pointer.style.setProperty(\"transition\", \"none\");\n                    }\n                    pointer.style.setProperty(\"width\", `${width}px`);\n                    pointer.style.setProperty(\"height\", `${height}px`);\n                }\n\n                if (!this.isOpen) {\n                    const { anchor } = this.props.pointerState;\n                    if (anchor === lastAnchor) {\n                        const { x, y, width } = anchor.getBoundingClientRect();\n                        const [lastAnchorX, lastAnchorY] = [anchorX, anchorY];\n                        [anchorX, anchorY] = [x, y];\n                        // Let's just say that the anchor is static if it moved less than 1px.\n                        const delta = Math.sqrt(\n                            Math.pow(x - lastAnchorX, 2) + Math.pow(y - lastAnchorY, 2)\n                        );\n                        if (delta < 1) {\n                            position.lock();\n                            return;\n                        }\n                        const wouldOverflow = window.innerWidth - x - width / 2 < dimensions?.width;\n                        pointer.classList.toggle(\"o_expand_left\", wouldOverflow);\n                    }\n                    lastAnchor = anchor;\n                    pointer.style.bottom = \"\";\n                    pointer.style.right = \"\";\n                    position.unlock();\n                }\n            } else {\n                lastMeasuredContent = null;\n                lastOpenState = false;\n                lastAnchor = null;\n                dimensions = null;\n            }\n        });\n    }\n\n    get content() {\n        return this.props.pointerState.content || \"\";\n    }\n\n    get isOpen() {\n        return this.props.pointerState.isOpen && this.content;\n    }\n\n    get position() {\n        return this.props.pointerState.position || \"top\";\n    }\n\n    async onStopClicked() {\n        await this.orm.call(\"res.users\", \"switch_tour_enabled\", [false]);\n        browser.location.reload();\n    }\n}\n", "import { reactive } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { TourPointer } from \"@web_tour/js/tour_pointer/tour_pointer\";\nimport { getScrollParent } from \"@web_tour/js/utils/tour_utils\";\n\n/**\n * @typedef {import(\"@web/core/position/position_hook\").Direction} Direction\n *\n * @typedef {\"in\" | \"out-below\" | \"out-above\" | \"unknown\"} IntersectionPosition\n *\n * @typedef {ReturnType<createPointerState>[\"methods\"]} TourPointerMethods\n *\n * @typedef TourPointerState\n * @property {HTMLElement} [anchor]\n * @property {string} [content]\n * @property {boolean} [isOpen]\n * @property {() => {}} [onClick]\n * @property {() => {}} [onMouseEnter]\n * @property {() => {}} [onMouseLeave]\n * @property {boolean} isVisible\n * @property {boolean} isZone\n * @property {Direction} position\n * @property {number} rev\n *\n * @typedef {import(\"../tour_service\").TourStep} TourStep\n */\n\nclass Intersection {\n    constructor() {\n        /** @type {Element | null} */\n        this.currentTarget = null;\n        this.rootBounds = null;\n        /** @type {IntersectionPosition} */\n        this._targetPosition = \"unknown\";\n        this._observer = new IntersectionObserver((observations) =>\n            this._handleObservations(observations)\n        );\n    }\n\n    /** @type {IntersectionObserverCallback} */\n    _handleObservations(observations) {\n        if (observations.length < 1) {\n            return;\n        }\n        const observation = observations[observations.length - 1];\n        this.rootBounds = observation.rootBounds;\n        if (this.rootBounds && this.currentTarget) {\n            if (observation.isIntersecting) {\n                this._targetPosition = \"in\";\n            } else {\n                const scrollParentElement =\n                    getScrollParent(this.currentTarget) || document.documentElement;\n                const targetBounds = this.currentTarget.getBoundingClientRect();\n                if (targetBounds.bottom > scrollParentElement.clientHeight) {\n                    this._targetPosition = \"out-below\";\n                } else if (targetBounds.top < 0) {\n                    this._targetPosition = \"out-above\";\n                } else if (targetBounds.left < 0) {\n                    this._targetPosition = \"out-left\";\n                } else if (targetBounds.right > scrollParentElement.clientWidth) {\n                    this._targetPosition = \"out-right\";\n                }\n            }\n        } else {\n            this._targetPosition = \"unknown\";\n        }\n    }\n\n    get targetPosition() {\n        if (!this.rootBounds) {\n            return this.currentTarget ? \"in\" : \"unknown\";\n        } else {\n            return this._targetPosition;\n        }\n    }\n\n    /**\n     * @param {Element} newTarget\n     */\n    setTarget(newTarget) {\n        if (this.currentTarget !== newTarget) {\n            if (this.currentTarget) {\n                this._observer.unobserve(this.currentTarget);\n            }\n            if (newTarget) {\n                this._observer.observe(newTarget);\n            }\n            this.currentTarget = newTarget;\n        }\n    }\n\n    stop() {\n        this._observer.disconnect();\n    }\n}\n\nexport function createPointerState() {\n    /**\n     * @param {Partial<TourPointerState>} newState\n     */\n    const setState = (newState) => {\n        Object.assign(state, newState);\n    };\n\n    /**\n     * @param {TourStep} step\n     * @param {HTMLElement} [anchor]\n     * @param {boolean} [isZone] will border de zone. e.g.: a dropzone\n     */\n    const pointTo = (anchor, step, isZone) => {\n        intersection.setTarget(anchor);\n        if (anchor) {\n            let { tooltipPosition, content } = step;\n            switch (intersection.targetPosition) {\n                case \"unknown\": {\n                    // Do nothing for unknown target position.\n                    break;\n                }\n                case \"in\": {\n                    if (document.body.contains(floatingAnchor)) {\n                        floatingAnchor.remove();\n                    }\n                    setState({\n                        anchor,\n                        content,\n                        isZone,\n                        onClick: null,\n                        position: tooltipPosition,\n                        isVisible: true,\n                    });\n                    break;\n                }\n                default: {\n                    const onClick = () => {\n                        anchor.scrollIntoView({ behavior: \"smooth\", block: \"nearest\" });\n                        hide();\n                    };\n\n                    const scrollParent = getScrollParent(anchor);\n                    if (!scrollParent) {\n                        setState({\n                            anchor,\n                            content,\n                            isZone,\n                            onClick: null,\n                            position: tooltipPosition,\n                            isVisible: true,\n                        });\n                        return;\n                    }\n                    let { x, y, width, height } = scrollParent.getBoundingClientRect();\n\n                    // If the scrolling element is within an iframe the offsets\n                    // must be computed taking into account the iframe.\n                    const iframeEl = scrollParent.ownerDocument.defaultView.frameElement;\n                    if (iframeEl) {\n                        const iframeOffset = iframeEl.getBoundingClientRect();\n                        x += iframeOffset.x;\n                        y += iframeOffset.y;\n                    }\n                    if (intersection.targetPosition === \"out-below\") {\n                        tooltipPosition = \"top\";\n                        content = _t(\"Scroll down to reach the next step.\");\n                        floatingAnchor.style.top = `${y + height - TourPointer.height}px`;\n                        floatingAnchor.style.left = `${x + width / 2}px`;\n                    } else if (intersection.targetPosition === \"out-above\") {\n                        tooltipPosition = \"bottom\";\n                        content = _t(\"Scroll up to reach the next step.\");\n                        floatingAnchor.style.top = `${y + TourPointer.height}px`;\n                        floatingAnchor.style.left = `${x + width / 2}px`;\n                    }\n                    if (intersection.targetPosition === \"out-left\") {\n                        tooltipPosition = \"right\";\n                        content = _t(\"Scroll left to reach the next step.\");\n                        floatingAnchor.style.top = `${y + height / 2}px`;\n                        floatingAnchor.style.left = `${x + TourPointer.width}px`;\n                    } else if (intersection.targetPosition === \"out-right\") {\n                        tooltipPosition = \"left\";\n                        content = _t(\"Scroll right to reach the next step.\");\n                        floatingAnchor.style.top = `${y + height / 2}px`;\n                        floatingAnchor.style.left = `${x + width - TourPointer.width}px`;\n                    }\n                    if (!document.contains(floatingAnchor)) {\n                        document.body.appendChild(floatingAnchor);\n                    }\n                    setState({\n                        anchor: floatingAnchor,\n                        content,\n                        onClick,\n                        position: tooltipPosition,\n                        isZone,\n                        isVisible: true,\n                    });\n                }\n            }\n        } else {\n            hide();\n        }\n    };\n\n    function hide() {\n        setState({ content: \"\", isVisible: false, isOpen: false });\n    }\n\n    function showContent(isOpen) {\n        setState({ isOpen });\n    }\n\n    function destroy() {\n        intersection.stop();\n        if (document.body.contains(floatingAnchor)) {\n            floatingAnchor.remove();\n        }\n    }\n\n    /** @type {TourPointerState} */\n    const state = reactive({});\n    const intersection = new Intersection();\n    const floatingAnchor = document.createElement(\"div\");\n    floatingAnchor.className = \"position-fixed\";\n\n    return { state, setState, showContent, pointTo, hide, destroy };\n}\n", "/**\n * Calls the given `func` then returns/resolves to `true`\n * if it will result to unloading of the page.\n * @param {(...args: any[]) => void} func\n * @param  {any[]} args\n * @returns {boolean | Promise<boolean>}\n */\nexport function callWithUnloadCheck(func, ...args) {\n    let willUnload = false;\n    const beforeunload = () => (willUnload = true);\n    window.addEventListener(\"beforeunload\", beforeunload);\n    const result = func(...args);\n    if (result instanceof Promise) {\n        return result.then(() => {\n            window.removeEventListener(\"beforeunload\", beforeunload);\n            return willUnload;\n        });\n    } else {\n        window.removeEventListener(\"beforeunload\", beforeunload);\n        return willUnload;\n    }\n}\n\nfunction formatValue(key, value, maxLength = 200) {\n    if (!value) {\n        return \"(empty)\";\n    }\n    return value.length > maxLength ? value.slice(0, maxLength) + \"...\" : value;\n}\n\nfunction serializeNode(node) {\n    if (node.nodeType === Node.TEXT_NODE) {\n        return `\"${node.nodeValue.trim()}\"`;\n    }\n    return node.outerHTML ? formatValue(\"node\", node.outerHTML, 500) : \"[Unknown Node]\";\n}\n\nexport function serializeChanges(snapshot, current) {\n    const changes = {\n        node: serializeNode(current),\n    };\n    function pushChanges(key, obj) {\n        changes[key] = changes[key] || [];\n        changes[key].push(obj);\n    }\n\n    if (snapshot.textContent !== current.textContent) {\n        pushChanges(\"modifiedText\", { before: snapshot.textContent, after: current.textContent });\n    }\n\n    const oldChildren = [...snapshot.childNodes].filter((node) => node.nodeType !== Node.TEXT_NODE);\n    const newChildren = [...current.childNodes].filter((node) => node.nodeType !== Node.TEXT_NODE);\n    oldChildren.forEach((oldNode, index) => {\n        if (!newChildren[index] || !oldNode.isEqualNode(newChildren[index])) {\n            pushChanges(\"removedNodes\", { oldNode: serializeNode(oldNode) });\n        }\n    });\n    newChildren.forEach((newNode, index) => {\n        if (!oldChildren[index] || !newNode.isEqualNode(oldChildren[index])) {\n            pushChanges(\"addedNodes\", { newNode: serializeNode(newNode) });\n        }\n    });\n\n    const oldAttrNames = new Set([...snapshot.attributes].map((attr) => attr.name));\n    const newAttrNames = new Set([...current.attributes].map((attr) => attr.name));\n    new Set([...oldAttrNames, ...newAttrNames]).forEach((attributeName) => {\n        const oldValue = snapshot.getAttribute(attributeName);\n        const newValue = current.getAttribute(attributeName);\n        const before = oldValue !== newValue || !newAttrNames.has(attributeName) ? oldValue : null;\n        const after = oldValue !== newValue || !oldAttrNames.has(attributeName) ? newValue : null;\n        if (before || after) {\n            pushChanges(\"modifiedAttributes\", { attributeName, before, after });\n        }\n    });\n    return changes;\n}\n\nexport function serializeMutation(mutation) {\n    const { type, attributeName } = mutation;\n    if (type === \"attributes\" && attributeName) {\n        return `attribute: ${attributeName}`;\n    } else {\n        return type;\n    }\n}\n\n/**\n * @param {HTMLElement} element\n * @returns {HTMLElement | null}\n */\nexport function getScrollParent(element) {\n    if (!element) {\n        return null;\n    }\n    // We cannot only rely on the fact that the element\u2019s scrollHeight is\n    // greater than its clientHeight. This might not be the case when a step\n    // starts, and the scrollbar could appear later. For example, when clicking\n    // on a \"building block\" in the \"building block previews modal\" during a\n    // tour (in website edit mode). When the modal opens, not all \"building\n    // blocks\" are loaded yet, and the scrollbar is not present initially.\n    const overflowY = window.getComputedStyle(element).overflowY;\n    const isScrollable =\n        overflowY === \"auto\" ||\n        overflowY === \"scroll\" ||\n        (overflowY === \"visible\" && element === element.ownerDocument.scrollingElement);\n    if (isScrollable) {\n        return element;\n    } else {\n        return getScrollParent(element.parentNode);\n    }\n}\n", "import { browser } from \"@web/core/browser/browser\";\n\nconst CURRENT_TOUR_LOCAL_STORAGE = \"current_tour\";\nconst CURRENT_TOUR_CONFIG_LOCAL_STORAGE = \"current_tour.config\";\nconst CURRENT_TOUR_INDEX_LOCAL_STORAGE = \"current_tour.index\";\nconst CURRENT_TOUR_ON_ERROR_LOCAL_STORAGE = \"current_tour.on_error\";\n\n/**\n * Wrapper around localStorage for persistence of the running tours.\n * Useful for resuming running tours when the page refreshed.\n */\nexport const tourState = {\n    getCurrentTour() {\n        return browser.localStorage.getItem(CURRENT_TOUR_LOCAL_STORAGE);\n    },\n    setCurrentTour(tourName) {\n        browser.localStorage.setItem(CURRENT_TOUR_LOCAL_STORAGE, tourName);\n    },\n    getCurrentIndex() {\n        const index = browser.localStorage.getItem(CURRENT_TOUR_INDEX_LOCAL_STORAGE, \"0\");\n        return parseInt(index, 10);\n    },\n    setCurrentIndex(index) {\n        browser.localStorage.setItem(CURRENT_TOUR_INDEX_LOCAL_STORAGE, index.toString());\n    },\n    getCurrentConfig() {\n        const config = browser.localStorage.getItem(CURRENT_TOUR_CONFIG_LOCAL_STORAGE, \"{}\");\n        return JSON.parse(config);\n    },\n    setCurrentConfig(config) {\n        config = JSON.stringify(config);\n        browser.localStorage.setItem(CURRENT_TOUR_CONFIG_LOCAL_STORAGE, config);\n    },\n    getCurrentTourOnError() {\n        return browser.localStorage.getItem(CURRENT_TOUR_ON_ERROR_LOCAL_STORAGE);\n    },\n    setCurrentTourOnError() {\n        browser.localStorage.setItem(CURRENT_TOUR_ON_ERROR_LOCAL_STORAGE, \"1\");\n    },\n    clear() {\n        browser.localStorage.removeItem(CURRENT_TOUR_ON_ERROR_LOCAL_STORAGE);\n        browser.localStorage.removeItem(CURRENT_TOUR_CONFIG_LOCAL_STORAGE);\n        browser.localStorage.removeItem(CURRENT_TOUR_INDEX_LOCAL_STORAGE);\n        browser.localStorage.removeItem(CURRENT_TOUR_LOCAL_STORAGE);\n    },\n};\n", "import { Component, markup, whenReady, validate } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { registry } from \"@web/core/registry\";\nimport { session } from \"@web/session\";\nimport { loadBundle } from \"@web/core/assets\";\nimport { createPointerState } from \"@web_tour/js/tour_pointer/tour_pointer_state\";\nimport { tourState } from \"@web_tour/js/tour_state\";\nimport { callWithUnloadCheck } from \"@web_tour/js/utils/tour_utils\";\nimport {\n    tourRecorderState,\n    TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY,\n} from \"@web_tour/js/tour_recorder/tour_recorder_state\";\nimport { redirect } from \"@web/core/utils/urls\";\n\nclass OnboardingItem extends Component {\n    static components = { DropdownItem };\n    static template = \"web_tour.OnboardingItem\";\n    static props = {\n        toursEnabled: { type: Boolean },\n        toggleItem: { type: Function },\n    };\n    setup() {}\n}\n\nconst StepSchema = {\n    id: { type: [String], optional: true },\n    content: { type: [String, Object], optional: true }, //allow object(_t && markup)\n    debugHelp: { type: String, optional: true },\n    isActive: { type: Array, element: String, optional: true },\n    run: { type: [String, Function, Boolean], optional: true },\n    timeout: {\n        optional: true,\n        validate(value) {\n            return value >= 0 && value <= 60000;\n        },\n    },\n    tooltipPosition: {\n        optional: true,\n        validate(value) {\n            return [\"top\", \"bottom\", \"left\", \"right\"].includes(value);\n        },\n    },\n    trigger: { type: String },\n    expectUnloadPage: { type: Boolean, optional: true },\n    //ONLY IN DEBUG MODE\n    pause: { type: Boolean, optional: true },\n    break: { type: Boolean, optional: true },\n};\n\nconst TourSchema = {\n    name: { type: String, optional: true },\n    steps: Function,\n    url: { type: String, optional: true },\n    wait_for: { type: [Function, Object], optional: true },\n};\n\nregistry.category(\"web_tour.tours\").addValidation(TourSchema);\nconst debugMenuRegistry = registry.category(\"debug\").category(\"default\");\n\nexport const tourService = {\n    // localization dependency to make sure translations used by tours are loaded\n    dependencies: [\"orm\", \"effect\", \"overlay\", \"localization\"],\n    start: async (env, { orm, effect, overlay }) => {\n        await whenReady();\n        let toursEnabled = session?.tour_enabled;\n        const tourRegistry = registry.category(\"web_tour.tours\");\n        const pointer = createPointerState();\n        pointer.stop = () => {};\n\n        debugMenuRegistry.add(\"onboardingItem\", () => ({\n            type: \"component\",\n            Component: OnboardingItem,\n            props: {\n                toursEnabled: toursEnabled || false,\n                toggleItem: async () => {\n                    tourState.clear();\n                    toursEnabled = await orm.call(\"res.users\", \"switch_tour_enabled\", [\n                        !toursEnabled,\n                    ]);\n                    browser.location.reload();\n                },\n            },\n            sequence: 500,\n            section: \"testing\",\n        }));\n\n        function getTourFromRegistry(tourName) {\n            if (!tourRegistry.contains(tourName)) {\n                return;\n            }\n            const tour = tourRegistry.get(tourName);\n            return {\n                ...tour,\n                steps: tour.steps(),\n                name: tourName,\n                wait_for: tour.wait_for || Promise.resolve(),\n            };\n        }\n\n        async function getTourFromDB(tourName) {\n            const tour = await orm.call(\"web_tour.tour\", \"get_tour_json_by_name\", [tourName]);\n            if (!tour) {\n                throw new Error(`Tour '${tourName}' is not found in the database.`);\n            }\n\n            if (!tour.steps.length && tourRegistry.contains(tour.name)) {\n                tour.steps = tourRegistry.get(tour.name).steps();\n            }\n\n            return tour;\n        }\n\n        function validateStep(step) {\n            try {\n                validate(step, StepSchema);\n            } catch (error) {\n                console.error(\n                    `Error in schema for TourStep ${JSON.stringify(step, null, 4)}\\n${\n                        error.message\n                    }`\n                );\n            }\n        }\n\n        async function startTour(tourName, options = {}) {\n            pointer.stop();\n            const tourFromRegistry = getTourFromRegistry(tourName);\n\n            if (!tourFromRegistry && !options.fromDB) {\n                // Sometime tours are not loaded depending on the modules.\n                // For example, point_of_sale do not load all tours assets.\n                return;\n            }\n\n            const tour = options.fromDB ? { name: tourName, url: options.url } : tourFromRegistry;\n            if (!session.is_public && !toursEnabled && options.mode === \"manual\") {\n                toursEnabled = await orm.call(\"res.users\", \"switch_tour_enabled\", [!toursEnabled]);\n            }\n\n            let tourConfig = {\n                delayToCheckUndeterminisms: 0,\n                stepDelay: 0,\n                keepWatchBrowser: false,\n                mode: \"auto\",\n                showPointerDuration: 0,\n                debug: false,\n                redirect: true,\n            };\n\n            tourConfig = Object.assign(tourConfig, options);\n            tourState.setCurrentConfig(tourConfig);\n            tourState.setCurrentTour(tour.name);\n            tourState.setCurrentIndex(0);\n\n            const willUnload = callWithUnloadCheck(() => {\n                if (tour.url && tourConfig.startUrl != tour.url && tourConfig.redirect) {\n                    redirect(tour.url);\n                }\n            });\n            if (!willUnload) {\n                await resumeTour();\n            }\n        }\n\n        async function resumeTour() {\n            const tourName = tourState.getCurrentTour();\n            const tourConfig = tourState.getCurrentConfig();\n\n            let tour = getTourFromRegistry(tourName);\n            if (tourConfig.fromDB) {\n                tour = await getTourFromDB(tourName);\n            }\n            if (!tour) {\n                return;\n            }\n\n            tour.steps.forEach((step) => validateStep(step));\n\n            if (tourConfig.mode === \"auto\") {\n                if (!odoo.loader.modules.get(\"@web_tour/js/tour_automatic/tour_automatic\")) {\n                    await loadBundle(\"web_tour.automatic\", { css: false });\n                }\n                const { TourAutomatic } = odoo.loader.modules.get(\n                    \"@web_tour/js/tour_automatic/tour_automatic\"\n                );\n                new TourAutomatic(tour).start();\n            } else {\n                await loadBundle(\"web_tour.interactive\");\n                const { TourPointer } = odoo.loader.modules.get(\n                    \"@web_tour/js/tour_pointer/tour_pointer\"\n                );\n                pointer.stop = overlay.add(\n                    TourPointer,\n                    {\n                        pointerState: pointer.state,\n                        bounce: !(tourConfig.mode === \"auto\" && tourConfig.keepWatchBrowser),\n                    },\n                    {\n                        sequence: 1100, // sequence based on bootstrap z-index values.\n                    }\n                );\n                const { TourInteractive } = odoo.loader.modules.get(\n                    \"@web_tour/js/tour_interactive/tour_interactive\"\n                );\n                new TourInteractive(tour).start(env, pointer, async () => {\n                    pointer.stop();\n                    tourState.clear();\n                    browser.console.log(\"tour succeeded\");\n                    let message = tourConfig.rainbowManMessage || tour.rainbowManMessage;\n                    if (message) {\n                        message = window.DOMPurify.sanitize(tourConfig.rainbowManMessage);\n                        effect.add({\n                            type: \"rainbow_man\",\n                            message: markup(message),\n                        });\n                    }\n\n                    const nextTour = await orm.call(\"web_tour.tour\", \"consume\", [tour.name]);\n                    if (nextTour) {\n                        startTour(nextTour.name, {\n                            mode: \"manual\",\n                            redirect: false,\n                            rainbowManMessage: nextTour.rainbowManMessage,\n                        });\n                    }\n                });\n            }\n        }\n\n        async function tourRecorder() {\n            await loadBundle(\"web_tour.recorder\");\n            const { TourRecorder } = odoo.loader.modules.get(\n                \"@web_tour/js/tour_recorder/tour_recorder\"\n            );\n            const remove = overlay.add(\n                TourRecorder,\n                {\n                    onClose: () => {\n                        remove();\n                        browser.localStorage.removeItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY);\n                        tourRecorderState.clear();\n                    },\n                },\n                { sequence: 99999 }\n            );\n        }\n\n        async function startTourRecorder() {\n            if (!browser.localStorage.getItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY)) {\n                await tourRecorder();\n            }\n            browser.localStorage.setItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY, \"1\");\n        }\n\n        if (!window.frameElement) {\n            const paramsTourName = new URLSearchParams(browser.location.search).get(\"tour\");\n            if (paramsTourName) {\n                startTour(paramsTourName, { mode: \"manual\", fromDB: true });\n            }\n\n            if (tourState.getCurrentTour()) {\n                if (tourState.getCurrentConfig().mode === \"auto\" || toursEnabled) {\n                    resumeTour();\n                } else {\n                    tourState.clear();\n                }\n            } else if (session.current_tour) {\n                startTour(session.current_tour.name, {\n                    mode: \"manual\",\n                    redirect: false,\n                    rainbowManMessage: session.current_tour.rainbowManMessage,\n                });\n            }\n\n            if (\n                browser.localStorage.getItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY) &&\n                !session.is_public\n            ) {\n                await tourRecorder();\n            }\n        }\n\n        odoo.startTour = startTour;\n        odoo.isTourReady = (tourName) => getTourFromRegistry(tourName).wait_for.then(() => true);\n\n        return {\n            startTour,\n            startTourRecorder,\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"tour_service\", tourService);\n", "import { browser } from \"@web/core/browser/browser\";\n\nconst CURRENT_TOUR_RECORDER_LOCAL_STORAGE = \"current_tour_recorder\";\nconst CURRENT_TOUR_RECORDER_RECORD_LOCAL_STORAGE = \"current_tour_recorder.record\";\nexport const TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY = \"tour_recorder_active\";\n\n/**\n * Wrapper around localStorage for persistence of the current recording.\n * Useful for resuming recording when the page refreshed.\n */\nexport const tourRecorderState = {\n    isRecording() {\n        return browser.localStorage.getItem(CURRENT_TOUR_RECORDER_RECORD_LOCAL_STORAGE) || \"0\";\n    },\n    setIsRecording(isRecording) {\n        browser.localStorage.setItem(\n            CURRENT_TOUR_RECORDER_RECORD_LOCAL_STORAGE,\n            isRecording ? \"1\" : \"0\"\n        );\n    },\n    setCurrentTourRecorder(tour) {\n        tour = JSON.stringify(tour);\n        browser.localStorage.setItem(CURRENT_TOUR_RECORDER_LOCAL_STORAGE, tour);\n    },\n    getCurrentTourRecorder() {\n        const tour = browser.localStorage.getItem(CURRENT_TOUR_RECORDER_LOCAL_STORAGE) || \"[]\";\n        return JSON.parse(tour);\n    },\n    clear() {\n        browser.localStorage.removeItem(CURRENT_TOUR_RECORDER_LOCAL_STORAGE);\n        browser.localStorage.removeItem(CURRENT_TOUR_RECORDER_RECORD_LOCAL_STORAGE);\n    },\n};\n", "import { _t } from \"@web/core/l10n/translation\";\n\nexport const stepUtils = {\n    _getHelpMessage(functionName, ...args) {\n        return `Generated by function tour utils ${functionName}(${args.join(\", \")})`;\n    },\n\n    addDebugHelp(helpMessage, step) {\n        if (typeof step.debugHelp === \"string\") {\n            step.debugHelp = step.debugHelp + \"\\n\" + helpMessage;\n        } else {\n            step.debugHelp = helpMessage;\n        }\n        return step;\n    },\n\n    editSelectMenuInput(trigger, value) {\n        return [\n            {\n                content: \"Make sure a SelectMenu has been opened\",\n                trigger: `.o_select_menu_menu`,\n            },\n            {\n                trigger,\n                async run({ queryFirst }) {\n                    const input = queryFirst(trigger);\n                    input.focus();\n                    input.value = value;\n                    input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n                    input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n                },\n            },\n        ];\n    },\n\n    showAppsMenuItem() {\n        return {\n            isActive: [\"auto\", \"community\", \"desktop\"],\n            trigger: \".o_navbar_apps_menu button:enabled\",\n            tooltipPosition: \"bottom\",\n            run: \"click\",\n        };\n    },\n\n    toggleHomeMenu() {\n        return [\n            {\n                isActive: [\".o_main_navbar .o_menu_toggle\"],\n                trigger: \".o_main_navbar .o_menu_toggle\",\n                content: _t(\"Click the top left corner to navigate across apps.\"),\n                tooltipPosition: \"bottom\",\n                run: \"click\",\n            },\n            {\n                isActive: [\"mobile\"],\n                trigger: \".o_sidebar_topbar a.btn-primary\",\n                tooltipPosition: \"right\",\n                run: \"click\",\n            },\n        ];\n    },\n\n    autoExpandMoreButtons(isActiveMobile = false) {\n        const isActive = [\"auto\"];\n        if (isActiveMobile) {\n            isActive.push(\"mobile\");\n        }\n        return {\n            isActive,\n            content: `autoExpandMoreButtons`,\n            trigger: \".o-form-buttonbox\",\n            async run({ queryFirst, click }) {\n                const more = queryFirst(\".o-form-buttonbox .o_button_more\");\n                if (more) {\n                    await click(more);\n                }\n            },\n        };\n    },\n\n    goToAppSteps(dataMenuXmlid, description) {\n        return [\n            this.showAppsMenuItem(),\n            {\n                isActive: [\"community\"],\n                trigger: `.o_app[data-menu-xmlid=\"${dataMenuXmlid}\"]`,\n                content: description,\n                tooltipPosition: \"right\",\n                run: \"click\",\n            },\n            {\n                isActive: [\"enterprise\"],\n                trigger: `.o_app[data-menu-xmlid=\"${dataMenuXmlid}\"]`,\n                content: description,\n                tooltipPosition: \"bottom\",\n                run: \"click\",\n            },\n        ].map((step) =>\n            this.addDebugHelp(this._getHelpMessage(\"goToApp\", dataMenuXmlid, description), step)\n        );\n    },\n\n    statusbarButtonsSteps(innerTextButton, description, trigger) {\n        const steps = [];\n        if (trigger) {\n            steps.push({\n                isActive: [\"auto\", \"mobile\"],\n                trigger,\n            });\n        }\n        steps.push(\n            {\n                isActive: [\"auto\", \"mobile\"],\n                trigger: \".o_statusbar_buttons\",\n                async run({ queryFirst, click }) {\n                    const buttonOutSideDropdownMenu = queryFirst(\n                        `.o_statusbar_buttons button:enabled:contains('${innerTextButton}')`\n                    );\n                    const node = queryFirst(\".o_statusbar_buttons button:has(.oi-ellipsis-v)\");\n                    if (!buttonOutSideDropdownMenu && node) {\n                        await click(node);\n                    }\n                },\n            },\n            {\n                trigger: `.o_statusbar_buttons button:enabled:contains('${innerTextButton}'), .dropdown-item button:enabled:contains('${innerTextButton}')`,\n                content: description,\n                tooltipPosition: \"bottom\",\n                run: \"click\",\n            }\n        );\n        return steps.map((step) =>\n            this.addDebugHelp(\n                this._getHelpMessage(\"statusbarButtonsSteps\", innerTextButton, description),\n                step\n            )\n        );\n    },\n\n    mobileKanbanSearchMany2X(modalTitle, valueSearched) {\n        return [\n            {\n                isActive: [\"mobile\"],\n                trigger: `.modal:not(.o_inactive_modal) .o_control_panel_navigation .btn .fa-search`,\n                tooltipPosition: \"bottom\",\n                run: \"click\",\n            },\n            {\n                isActive: [\"mobile\"],\n                trigger: \".o_searchview_input\",\n                tooltipPosition: \"bottom\",\n                run: `edit ${valueSearched}`,\n            },\n            {\n                isActive: [\"mobile\"],\n                trigger: \".dropdown-menu.o_searchview_autocomplete\",\n            },\n            {\n                isActive: [\"mobile\"],\n                trigger: \".o_searchview_input\",\n                tooltipPosition: \"bottom\",\n                run: \"press Enter\",\n            },\n            {\n                isActive: [\"mobile\"],\n                trigger: `.o_kanban_record:contains('${valueSearched}')`,\n                tooltipPosition: \"bottom\",\n                run: \"click\",\n            },\n        ].map((step) =>\n            this.addDebugHelp(\n                this._getHelpMessage(\"mobileKanbanSearchMany2X\", modalTitle, valueSearched),\n                step\n            )\n        );\n    },\n    /**\n     * Utility steps to save a form and wait for the save to complete\n     */\n    saveForm() {\n        return [\n            {\n                isActive: [\"auto\"],\n                content: \"save form\",\n                trigger: \".o_form_button_save:enabled\",\n                run: \"click\",\n            },\n            {\n                content: \"wait for save completion\",\n                trigger: \".o_form_readonly, .o_form_saved\",\n            },\n        ];\n    },\n    /**\n     * Utility steps to cancel a form creation or edition.\n     *\n     * Supports creation/edition from either a form or a list view (so checks\n     * for both states).\n     */\n    discardForm() {\n        return [\n            {\n                isActive: [\"auto\"],\n                content: \"discard the form\",\n                trigger: \".o_form_button_cancel\",\n                run: \"click\",\n            },\n            {\n                content: \"wait for cancellation to complete\",\n                trigger:\n                    \".o_view_controller.o_list_view, .o_form_view > div > main > .o_form_readonly, .o_form_view > div > main > .o_form_saved\",\n            },\n        ];\n    },\n\n    waitIframeIsReady() {\n        return {\n            content: \"Wait until the iframe is ready\",\n            trigger: `:iframe body[is-ready=true]`,\n        };\n    },\n\n    goToUrl(url) {\n        return {\n            isActive: [\"auto\"],\n            content: `Navigate to ${url}`,\n            trigger: \"body\",\n            run: `goToUrl ${url}`,\n            expectUnloadPage: true,\n        };\n    },\n};\n", "import { Component, xml } from \"@odoo/owl\";\n\nconst NO_OP = () => {};\n\nexport class Switch extends Component {\n    static props = {\n        value: { type: Boolean, optional: true },\n        extraClasses: String,\n        disabled: { type: Boolean, optional: true },\n        label: { type: String, optional: true },\n        description: { type: String, optional: true },\n        onChange: { Function, optional: true },\n    };\n    static defaultProps = {\n        onChange: NO_OP,\n    };\n    static template = xml`\n    <label t-att-class=\"'o_switch' + extraClasses\">\n        <input type=\"checkbox\"\n                name=\"switch\"\n                class=\"visually-hidden\"\n                t-att-checked=\"props.value\"\n                t-att-disabled=\"props.disabled\"\n                t-on-change=\"(ev) => props.onChange(ev.target.checked)\"\n                t-on-keyup=\"onKeyup\"/>\n        <span/>\n        <span t-if=\"props.label\" t-esc=\"props.label\" class=\"ms-2\"/>\n        <span t-if=\"props.description\" class=\"text-muted ms-2\" t-esc=\"props.description\"/>\n    </label>\n    `;\n\n    setup() {\n        this.extraClasses = this.props.extraClasses ? ` ${this.props.extraClasses}` : \"\";\n    }\n    /**\n     * @param {KeyboardEvent} ev\n     */\n    onKeyup(ev) {\n        // \"Enter\" is not a default on checkboxes, but as the switch doesn't\n        // look like a checkbox anymore, we support it.\n        if (ev.key === \"Enter\") {\n            ev.currentTarget.checked = !ev.currentTarget.checked;\n        }\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Attachment, FileSelector, IMAGE_MIMETYPES } from \"./file_selector\";\nimport { renderToElement } from \"@web/core/utils/render\";\n\nexport class DocumentAttachment extends Attachment {\n    static template = \"html_editor.DocumentAttachment\";\n}\n\nexport class DocumentSelector extends FileSelector {\n    static mediaSpecificClasses = [\"o_image\"];\n    static mediaSpecificStyles = [];\n    static mediaExtraClasses = [];\n    static tagNames = [\"A\"];\n    static attachmentsListTemplate = \"html_editor.DocumentsListTemplate\";\n    static components = {\n        ...FileSelector.components,\n        DocumentAttachment,\n    };\n\n    setup() {\n        super.setup();\n\n        this.uploadText = _t(\"Upload a document\");\n        this.urlPlaceholder = \"https://www.odoo.com/mydocument\";\n        this.addText = _t(\"Add URL\");\n        this.searchPlaceholder = _t(\"Search a document\");\n        this.allLoadedText = _t(\"All documents have been loaded\");\n    }\n\n    get attachmentsDomain() {\n        const domain = super.attachmentsDomain;\n        domain.push([\"mimetype\", \"not in\", IMAGE_MIMETYPES]);\n        // The assets should not be part of the documents.\n        // All assets begin with '/web/assets/', see _get_asset_template_url().\n        domain.unshift(\"&\", \"|\", [\"url\", \"=\", null], \"!\", [\"url\", \"=like\", \"/web/assets/%\"]);\n        return domain;\n    }\n\n    async onClickDocument(document) {\n        this.selectAttachment(document);\n        await this.props.save();\n    }\n\n    async fetchAttachments(...args) {\n        const attachments = await super.fetchAttachments(...args);\n\n        if (this.selectInitialMedia()) {\n            for (const attachment of attachments) {\n                if (\n                    `/web/content/${attachment.id}` ===\n                    this.props.media.getAttribute(\"href\").replace(/[?].*/, \"\")\n                ) {\n                    this.selectAttachment(attachment);\n                }\n            }\n        }\n        return attachments;\n    }\n\n    /**\n     * Utility method used by the MediaDialog component.\n     */\n    static async createElements(selectedMedia, { orm }) {\n        return Promise.all(\n            selectedMedia.map(async (attachment) => {\n                let url = `/web/content/${encodeURIComponent(\n                    attachment.id\n                )}?unique=${encodeURIComponent(attachment.checksum)}&download=true`;\n                if (!attachment.public) {\n                    let accessToken = attachment.access_token;\n                    if (!accessToken) {\n                        [accessToken] = await orm.call(\"ir.attachment\", \"generate_access_token\", [\n                            attachment.id,\n                        ]);\n                    }\n                    url += `&access_token=${encodeURIComponent(accessToken)}`;\n                }\n                return this.renderFileElement(attachment, url);\n            })\n        );\n    }\n\n    static renderFileElement(attachment, downloadUrl) {\n        return renderStaticFileBox(\n            attachment.name,\n            attachment.mimetype,\n            downloadUrl,\n            attachment.id\n        );\n    }\n}\n\nexport function renderStaticFileBox(filename, mimetype, downloadUrl, id) {\n    const rootSpan = document.createElement(\"span\");\n    rootSpan.classList.add(\"o_file_box\", \"o-contenteditable-false\");\n    rootSpan.contentEditable = false;\n    rootSpan.dataset.attachmentId = id;\n    const bannerElement = renderToElement(\"html_editor.StaticFileBox\", {\n        fileModel: { filename, mimetype, downloadUrl },\n    });\n    rootSpan.append(bannerElement);\n    return rootSpan;\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { ConfirmationDialog } from \"@web/core/confirmation_dialog/confirmation_dialog\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { useDebounced } from \"@web/core/utils/timing\";\nimport { SearchMedia } from \"./search_media\";\n\nimport { Component, xml, useState, useRef, onWillStart, useEffect } from \"@odoo/owl\";\n\nexport const IMAGE_MIMETYPES = [\n    \"image/jpg\",\n    \"image/jpeg\",\n    \"image/jpe\",\n    \"image/png\",\n    \"image/svg+xml\",\n    \"image/gif\",\n    \"image/webp\",\n];\nexport const IMAGE_EXTENSIONS = [\".jpg\", \".jpeg\", \".jpe\", \".png\", \".svg\", \".gif\", \".webp\"];\n\nclass RemoveButton extends Component {\n    static template = xml`<i class=\"fa fa-trash o_existing_attachment_remove position-absolute top-0 end-0 p-2 bg-white-25 cursor-pointer opacity-0 opacity-100-hover z-1 transition-base\" t-att-title=\"removeTitle\" role=\"img\" t-att-aria-label=\"removeTitle\" t-on-click=\"this.remove\"/>`;\n    static props = [\"model?\", \"remove\"];\n    setup() {\n        this.removeTitle = _t(\"This file is attached to the current record.\");\n        if (this.props.model === \"ir.ui.view\") {\n            this.removeTitle = _t(\"This file is a public view attachment.\");\n        }\n    }\n\n    remove(ev) {\n        ev.stopPropagation();\n        this.props.remove();\n    }\n}\n\nexport class AttachmentError extends Component {\n    static components = { Dialog };\n    static template = \"html_editor.AttachmentError\";\n    static props = [\"views\", \"close\"];\n    setup() {\n        this.title = _t(\"Alert\");\n    }\n}\n\nexport class Attachment extends Component {\n    static template = \"\";\n    static components = {\n        RemoveButton,\n    };\n    static props = [\"*\"];\n    setup() {\n        this.dialogs = useService(\"dialog\");\n    }\n\n    remove() {\n        this.dialogs.add(ConfirmationDialog, {\n            body: _t(\"Are you sure you want to delete this file?\"),\n            confirm: async () => {\n                const prevented = await rpc(\"/html_editor/attachment/remove\", {\n                    ids: [this.props.id],\n                });\n                if (!Object.keys(prevented).length) {\n                    this.props.onRemoved(this.props.id);\n                } else {\n                    this.dialogs.add(AttachmentError, {\n                        views: prevented[this.props.id],\n                    });\n                }\n            },\n        });\n    }\n}\n\nexport class FileSelectorControlPanel extends Component {\n    static template = \"html_editor.FileSelectorControlPanel\";\n    static components = {\n        SearchMedia,\n    };\n    static props = {\n        uploadUrl: Function,\n        validateUrl: Function,\n        uploadFiles: Function,\n        changeSearchService: Function,\n        changeShowOptimized: Function,\n        search: Function,\n        accept: { type: String, optional: true },\n        addText: { type: String, optional: true },\n        multiSelect: { type: true, optional: true },\n        needle: { type: String, optional: true },\n        searchPlaceholder: { type: String, optional: true },\n        searchService: { type: String, optional: true },\n        showOptimized: { type: Boolean, optional: true },\n        showOptimizedOption: { type: String, optional: true },\n        uploadText: { type: String, optional: true },\n        urlPlaceholder: { type: String, optional: true },\n        urlWarningTitle: { type: String, optional: true },\n        useMediaLibrary: { type: Boolean, optional: true },\n        useUnsplash: { type: Boolean, optional: true },\n    };\n    setup() {\n        this.state = useState({\n            showUrlInput: false,\n            urlInput: \"\",\n            isValidUrl: false,\n            isValidFileFormat: false,\n            isValidatingUrl: false,\n        });\n        this.debouncedValidateUrl = useDebounced(this.props.validateUrl, 500);\n\n        this.fileInput = useRef(\"file-input\");\n        const urlInputRef = useRef(\"urlInput\");\n\n        useEffect(\n            () => {\n                if (this.state.showUrlInput) {\n                    urlInputRef.el.focus();\n                }\n            },\n            () => [this.state.showUrlInput]\n        );\n    }\n\n    get showSearchServiceSelect() {\n        return this.props.searchService && this.props.needle;\n    }\n\n    get enableUrlUploadClick() {\n        return (\n            !this.state.showUrlInput ||\n            (this.state.urlInput && this.state.isValidUrl && this.state.isValidFileFormat)\n        );\n    }\n\n    async onUrlUploadClick() {\n        if (!this.state.showUrlInput) {\n            this.state.showUrlInput = true;\n        } else {\n            await this.props.uploadUrl(this.state.urlInput);\n            this.state.urlInput = \"\";\n        }\n    }\n\n    async onUrlInput(ev) {\n        this.state.isValidatingUrl = true;\n        const { isValidUrl, isValidFileFormat } = await this.debouncedValidateUrl(ev.target.value);\n        this.state.isValidFileFormat = isValidFileFormat;\n        this.state.isValidUrl = isValidUrl;\n        this.state.isValidatingUrl = false;\n    }\n\n    onClickUpload() {\n        this.fileInput.el.click();\n    }\n\n    async onChangeFileInput() {\n        const inputFiles = this.fileInput.el.files;\n        if (!inputFiles.length) {\n            return;\n        }\n        await this.props.uploadFiles(inputFiles);\n        const fileInputEl = this.fileInput.el;\n        if (fileInputEl) {\n            fileInputEl.value = \"\";\n        }\n    }\n}\n\nexport class FileSelector extends Component {\n    static template = \"html_editor.FileSelector\";\n    static components = {\n        FileSelectorControlPanel,\n    };\n    static props = [\"*\"];\n\n    setup() {\n        this.notificationService = useService(\"notification\");\n        this.orm = useService(\"orm\");\n        this.uploadService = useService(\"upload\");\n        this.keepLast = new KeepLast();\n\n        this.loadMoreButtonRef = useRef(\"load-more-button\");\n        this.existingAttachmentsRef = useRef(\"existing-attachments\");\n\n        this.state = useState({\n            attachments: [],\n            canScrollAttachments: false,\n            canLoadMoreAttachments: false,\n            isFetchingAttachments: false,\n            needle: \"\",\n        });\n\n        this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY = 30;\n\n        onWillStart(async () => {\n            this.state.attachments = await this.fetchAttachments(\n                this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY,\n                0\n            );\n        });\n\n        this.debouncedOnScroll = useDebounced(this.updateScroll, 15);\n        this.debouncedScrollUpdate = useDebounced(this.updateScroll, 500);\n\n        useEffect(\n            (modalEl) => {\n                if (modalEl) {\n                    modalEl.addEventListener(\"scroll\", this.debouncedOnScroll);\n                    return () => {\n                        modalEl.removeEventListener(\"scroll\", this.debouncedOnScroll);\n                    };\n                }\n            },\n            () => [this.props.modalRef.el?.querySelector(\"main.modal-body\")]\n        );\n\n        useEffect(\n            () => {\n                // Updating the scroll button each time the attachments change.\n                // Hiding the \"Load more\" button to prevent it from flickering.\n                this.loadMoreButtonRef.el.classList.add(\"o_hide_loading\");\n                this.state.canScrollAttachments = false;\n                this.debouncedScrollUpdate();\n            },\n            () => [this.allAttachments.length]\n        );\n    }\n\n    get canLoadMore() {\n        return this.state.canLoadMoreAttachments;\n    }\n\n    get hasContent() {\n        return this.state.attachments.length;\n    }\n\n    get isFetching() {\n        return this.state.isFetchingAttachments;\n    }\n\n    get selectedAttachmentIds() {\n        return this.props.selectedMedia[this.props.id]\n            .filter((media) => media.mediaType === \"attachment\")\n            .map(({ id }) => id);\n    }\n\n    get attachmentsDomain() {\n        const domain = [\n            \"&\",\n            [\"res_model\", \"=\", this.props.resModel],\n            [\"res_id\", \"=\", this.props.resId || 0],\n        ];\n        domain.unshift(\"|\", [\"public\", \"=\", true]);\n        domain.push([\"name\", \"ilike\", this.state.needle]);\n        return domain;\n    }\n\n    get allAttachments() {\n        return this.state.attachments;\n    }\n\n    validateUrl(url) {\n        const path = url.split(\"?\")[0];\n        const isValidUrl = /^.+\\..+$/.test(path); // TODO improve\n        const isValidFileFormat = true;\n        return { isValidUrl, isValidFileFormat, path };\n    }\n\n    async fetchAttachments(limit, offset) {\n        this.state.isFetchingAttachments = true;\n        let attachments = [];\n        try {\n            attachments = await this.orm.call(\"ir.attachment\", \"search_read\", [], {\n                domain: this.attachmentsDomain,\n                fields: [\n                    \"name\",\n                    \"mimetype\",\n                    \"description\",\n                    \"checksum\",\n                    \"url\",\n                    \"type\",\n                    \"res_id\",\n                    \"res_model\",\n                    \"public\",\n                    \"access_token\",\n                    \"image_src\",\n                    \"image_width\",\n                    \"image_height\",\n                    \"original_id\",\n                ],\n                order: \"id desc\",\n                // Try to fetch first record of next page just to know whether there is a next page.\n                limit,\n                offset,\n            });\n            attachments.forEach((attachment) => (attachment.mediaType = \"attachment\"));\n        } catch (e) {\n            // Reading attachments as a portal user is not permitted and will raise\n            // an access error so we catch the error silently and don't return any\n            // attachment so he can still use the wizard and upload an attachment\n            if (e.exceptionName !== \"odoo.exceptions.AccessError\") {\n                throw e;\n            }\n        }\n        this.state.canLoadMoreAttachments =\n            attachments.length >= this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;\n        this.state.isFetchingAttachments = false;\n        return attachments;\n    }\n\n    async handleLoadMore() {\n        await this.loadMore();\n    }\n\n    async loadMore() {\n        return this.keepLast\n            .add(\n                this.fetchAttachments(\n                    this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY,\n                    this.state.attachments.length\n                )\n            )\n            .then((newAttachments) => {\n                // This is never reached if another search or loadMore occurred.\n                this.state.attachments.push(...newAttachments);\n            });\n    }\n\n    async handleSearch(needle) {\n        await this.search(needle);\n    }\n\n    async search(needle) {\n        // Prepare in case loadMore results are obtained instead.\n        this.state.attachments = [];\n        // Fetch attachments relies on the state's needle.\n        this.state.needle = needle;\n        return this.keepLast\n            .add(this.fetchAttachments(this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY, 0))\n            .then((attachments) => {\n                // This is never reached if a new search occurred.\n                this.state.attachments = attachments;\n            });\n    }\n\n    async uploadFiles(files) {\n        await this.uploadService.uploadFiles(\n            files,\n            { resModel: this.props.resModel, resId: this.props.resId },\n            (attachment) => this.onUploaded(attachment)\n        );\n    }\n\n    async uploadUrl(url) {\n        await fetch(url)\n            .then(async (result) => {\n                const blob = await result.blob();\n                blob.id = new Date().getTime();\n                blob.name = new URL(url, window.location.href).pathname\n                    .split(\"/\")\n                    .findLast((s) => s);\n                await this.uploadFiles([blob]);\n            })\n            .catch(async () => {\n                await new Promise((resolve) => {\n                    // If it works from an image, use URL.\n                    const imageEl = document.createElement(\"img\");\n                    imageEl.onerror = () => {\n                        // This message is about the blob fetch failure.\n                        // It is only displayed if the fallback did not work.\n                        this.notificationService.add(\n                            _t(\"An error occurred while fetching the entered URL.\"),\n                            {\n                                type: \"danger\",\n                                sticky: true,\n                            }\n                        );\n                        resolve();\n                    };\n                    imageEl.onload = () => {\n                        this.onLoadUploadedUrl(url, resolve);\n                    };\n                    imageEl.src = url;\n                });\n            });\n    }\n\n    async onLoadUploadedUrl(url, resolve) {\n        await this.uploadService.uploadUrl(\n            url,\n            {\n                resModel: this.props.resModel,\n                resId: this.props.resId,\n            },\n            (attachment) => this.onUploaded(attachment)\n        );\n        resolve();\n    }\n\n    async onUploaded(attachment) {\n        this.state.attachments = [\n            attachment,\n            ...this.state.attachments.filter((attach) => attach.id !== attachment.id),\n        ];\n        this.selectAttachment(attachment);\n        if (!this.props.multiSelect) {\n            await this.props.save();\n        }\n        if (this.props.onAttachmentChange) {\n            this.props.onAttachmentChange(attachment);\n        }\n    }\n\n    onRemoved(attachmentId) {\n        this.state.attachments = this.state.attachments.filter(\n            (attachment) => attachment.id !== attachmentId\n        );\n    }\n\n    selectAttachment(attachment) {\n        this.props.selectMedia({ ...attachment, mediaType: \"attachment\" });\n    }\n\n    selectInitialMedia() {\n        return (\n            this.props.media &&\n            this.constructor.tagNames.includes(this.props.media.tagName) &&\n            !this.selectedAttachmentIds.length\n        );\n    }\n\n    /**\n     * Updates the scroll button, depending on whether the \"Load more\" button is\n     * fully visible or not.\n     */\n    updateScroll() {\n        const loadMoreTop = this.loadMoreButtonRef.el.getBoundingClientRect().top;\n        const modalEl = this.props.modalRef.el.querySelector(\"main.modal-body\");\n        const modalBottom = modalEl.getBoundingClientRect().bottom;\n        this.state.canScrollAttachments = loadMoreTop >= modalBottom;\n        this.loadMoreButtonRef.el.classList.remove(\"o_hide_loading\");\n    }\n\n    /**\n     * Checks if the attachment is (partially) hidden.\n     *\n     * @param {Element} attachmentEl the attachment \"container\"\n     * @returns {Boolean} true if the attachment is hidden, false otherwise.\n     */\n    isAttachmentHidden(attachmentEl) {\n        const attachmentBottom = Math.round(attachmentEl.getBoundingClientRect().bottom);\n        const modalEl = this.props.modalRef.el.querySelector(\"main.modal-body\");\n        const modalBottom = modalEl.getBoundingClientRect().bottom;\n        return attachmentBottom > modalBottom;\n    }\n\n    /**\n     * Scrolls two attachments rows at a time. If there are not enough rows,\n     * scrolls to the \"Load more\" button.\n     */\n    handleScrollAttachments() {\n        let scrollToEl = this.loadMoreButtonRef.el;\n        const attachmentEls = [\n            ...this.existingAttachmentsRef.el.querySelectorAll(\".o_existing_attachment_cell\"),\n        ];\n        const firstHiddenAttachmentEl = attachmentEls.find((el) => this.isAttachmentHidden(el));\n        if (firstHiddenAttachmentEl) {\n            const attachmentBottom = firstHiddenAttachmentEl.getBoundingClientRect().bottom;\n            const attachmentIndex = attachmentEls.indexOf(firstHiddenAttachmentEl);\n            const firstNextRowAttachmentEl = attachmentEls\n                .slice(attachmentIndex)\n                .find((el) => el.getBoundingClientRect().bottom > attachmentBottom);\n            scrollToEl = firstNextRowAttachmentEl || scrollToEl;\n        }\n        scrollToEl.scrollIntoView({ block: \"end\", inline: \"nearest\", behavior: \"smooth\" });\n    }\n}\n", "import { SearchMedia } from \"./search_media\";\nimport { fonts } from \"@html_editor/utils/fonts\";\n\nimport { Component, useState } from \"@odoo/owl\";\n\nexport class IconSelector extends Component {\n    static mediaSpecificClasses = [\"fa\"];\n    static mediaSpecificStyles = [\"color\", \"background-color\"];\n    static mediaExtraClasses = [\n        \"rounded-circle\",\n        \"rounded\",\n        \"img-thumbnail\",\n        \"shadow\",\n        /^text-\\S+$/,\n        /^bg-\\S+$/,\n        /^fa-\\S+$/,\n    ];\n    static tagNames = [\"SPAN\", \"I\"];\n    static template = \"html_editor.IconSelector\";\n    static components = {\n        SearchMedia,\n    };\n    static props = [\"*\"];\n\n    setup() {\n        this.state = useState({\n            fonts: this.props.fonts,\n            needle: \"\",\n        });\n    }\n\n    get selectedMediaIds() {\n        return this.props.selectedMedia[this.props.id].map(({ id }) => id);\n    }\n\n    search(needle) {\n        this.state.needle = needle;\n        if (!this.state.needle) {\n            this.state.fonts = this.props.fonts;\n        } else {\n            this.state.fonts = this.props.fonts.map((font) => {\n                const icons = font.icons.filter(\n                    (icon) => icon.alias.indexOf(this.state.needle.toLowerCase()) >= 0\n                );\n                return { ...font, icons };\n            });\n        }\n    }\n\n    async onClickIcon(font, icon) {\n        this.props.selectMedia({\n            ...icon,\n            fontBase: font.base,\n            // To check if the icon has changed, we only need to compare\n            // an alias of the icon with the class from the old media (some\n            // icons can have multiple classes e.g. \"fa-gears\" ~ \"fa-cogs\")\n            initialIconChanged:\n                this.props.media &&\n                !icon.names.some((name) => this.props.media.classList.contains(name)),\n        });\n        await this.props.save();\n    }\n\n    /**\n     * Utility methods, used by the MediaDialog component.\n     */\n    static createElements(selectedMedia) {\n        return selectedMedia.map((icon) => {\n            const iconEl = document.createElement(\"span\");\n            iconEl.classList.add(icon.fontBase, icon.names[0]);\n            return iconEl;\n        });\n    }\n    static initFonts() {\n        fonts.computeFonts();\n        const allFonts = fonts.fontIcons.map(({ cssData, base }) => {\n            const uniqueIcons = Array.from(\n                new Map(\n                    cssData.map((icon) => {\n                        const alias = icon.names.join(\",\");\n                        const id = `${base}_${alias}`;\n                        return [id, { ...icon, alias, id }];\n                    })\n                ).values()\n            );\n            return { base, icons: uniqueIcons };\n        });\n        return allFonts;\n    }\n}\n", "import { useRef, useState } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { DEFAULT_PALETTE } from \"@html_editor/utils/color\";\nimport { getCSSVariableValue, getHtmlStyle } from \"@html_editor/utils/formatting\";\nimport { Attachment, FileSelector, IMAGE_EXTENSIONS, IMAGE_MIMETYPES } from \"./file_selector\";\nimport { isSrcCorsProtected } from \"@html_editor/utils/image\";\n\nexport class AutoResizeImage extends Attachment {\n    static template = \"html_editor.AutoResizeImage\";\n    setup() {\n        super.setup();\n\n        this.image = useRef(\"auto-resize-image\");\n        this.container = useRef(\"auto-resize-image-container\");\n\n        this.state = useState({\n            loaded: false,\n        });\n    }\n\n    async onImageLoaded() {\n        if (!this.image.el) {\n            // Do not fail if already removed.\n            return;\n        }\n        if (this.props.onLoaded) {\n            await this.props.onLoaded(this.image.el);\n            if (!this.image.el) {\n                // If replaced by colored version, aspect ratio will be\n                // computed on it instead.\n                return;\n            }\n        }\n        const aspectRatio = this.image.el.offsetWidth / this.image.el.offsetHeight;\n        const width = aspectRatio * this.props.minRowHeight;\n        this.container.el.style.flexGrow = width;\n        this.container.el.style.flexBasis = `${width}px`;\n        this.state.loaded = true;\n    }\n}\nconst newLocal = \"img-fluid\";\nexport class ImageSelector extends FileSelector {\n    static mediaSpecificClasses = [\"img\", newLocal, \"o_we_custom_image\"];\n    static mediaSpecificStyles = [];\n    static mediaExtraClasses = [\n        \"rounded-circle\",\n        \"rounded\",\n        \"img-thumbnail\",\n        \"shadow\",\n        \"w-25\",\n        \"w-50\",\n        \"w-75\",\n        \"w-100\",\n    ];\n    static tagNames = [\"IMG\"];\n    static attachmentsListTemplate = \"html_editor.ImagesListTemplate\";\n    static components = {\n        ...FileSelector.components,\n        AutoResizeImage,\n    };\n\n    setup() {\n        super.setup();\n\n        this.keepLastLibraryMedia = new KeepLast();\n\n        this.state.libraryMedia = [];\n        this.state.libraryResults = null;\n        this.state.isFetchingLibrary = false;\n        this.state.searchService = \"all\";\n        this.state.showOptimized = false;\n        this.NUMBER_OF_MEDIA_TO_DISPLAY = 10;\n\n        this.uploadText = _t(\"Upload an image\");\n        this.urlPlaceholder = \"https://www.odoo.com/logo.png\";\n        this.addText = _t(\"Add URL\");\n        this.searchPlaceholder = _t(\"Search an image\");\n        this.urlWarningTitle = _t(\n            \"Uploaded image's format is not supported. Try with: \" + IMAGE_EXTENSIONS.join(\", \")\n        );\n        this.allLoadedText = _t(\"All images have been loaded\");\n        this.showOptimizedOption = this.env.debug;\n        this.MIN_ROW_HEIGHT = 128;\n\n        this.fileMimetypes = IMAGE_MIMETYPES.join(\",\");\n        this.isImageField =\n            !!this.props.media?.closest(\"[data-oe-type=image]\") || !!this.props.addFieldImage;\n    }\n\n    get canLoadMore() {\n        // The user can load more library media only when the filter is set.\n        if (this.state.searchService === \"media-library\") {\n            return (\n                this.state.libraryResults &&\n                this.state.libraryMedia.length < this.state.libraryResults\n            );\n        }\n        return super.canLoadMore;\n    }\n\n    get hasContent() {\n        if (this.state.searchService === \"all\") {\n            return super.hasContent || !!this.state.libraryMedia.length;\n        } else if (this.state.searchService === \"media-library\") {\n            return !!this.state.libraryMedia.length;\n        }\n        return super.hasContent;\n    }\n\n    get isFetching() {\n        return super.isFetching || this.state.isFetchingLibrary;\n    }\n\n    get selectedMediaIds() {\n        return this.props.selectedMedia[this.props.id]\n            .filter((media) => media.mediaType === \"libraryMedia\")\n            .map(({ id }) => id);\n    }\n\n    get allAttachments() {\n        return [...super.allAttachments, ...this.state.libraryMedia];\n    }\n\n    get attachmentsDomain() {\n        const domain = super.attachmentsDomain;\n        domain.push([\"mimetype\", \"in\", IMAGE_MIMETYPES]);\n        if (!this.props.useMediaLibrary) {\n            domain.push(\n                \"|\",\n                [\"url\", \"=\", false],\n                \"!\",\n                \"|\",\n                [\"url\", \"=ilike\", \"/html_editor/shape/%\"],\n                [\"url\", \"=ilike\", \"/web_editor/shape/%\"]\n            );\n        }\n        domain.push(\"!\", [\"name\", \"=like\", \"%.crop\"]);\n        domain.push(\"|\", [\"type\", \"=\", \"binary\"], \"!\", [\"url\", \"=like\", \"/%/static/%\"]);\n\n        // Optimized images (meaning they are related to an `original_id`) can\n        // only be shown in debug mode as the toggler to make those images\n        // appear is hidden when not in debug mode.\n        // There is thus no point to fetch those optimized images outside debug\n        // mode. Worst, it leads to bugs: it might fetch only optimized images\n        // when clicking on \"load more\" which will look like it's bugged as no\n        // images will appear on screen (they all will be hidden).\n        if (!this.env.debug) {\n            const subDomain = [false];\n\n            // Particular exception: if the edited image is an optimized\n            // image, we need to fetch it too so it's displayed as the\n            // selected image when opening the media dialog.\n            // We might get a few more optimized image than necessary if the\n            // original image has multiple optimized images but it's not a\n            // big deal.\n            const originalId = this.props.media && this.props.media.dataset.originalId;\n            if (originalId) {\n                subDomain.push(originalId);\n            }\n\n            domain.push([\"original_id\", \"in\", subDomain]);\n        }\n\n        return domain;\n    }\n\n    async uploadFiles(files) {\n        await this.uploadService.uploadFiles(\n            files,\n            { resModel: this.props.resModel, resId: this.props.resId, isImage: true },\n            (attachment) => this.onUploaded(attachment)\n        );\n    }\n\n    async validateUrl(...args) {\n        const { isValidUrl, path } = super.validateUrl(...args);\n        const isValidFileFormat =\n            isValidUrl &&\n            (await new Promise((resolve) => {\n                const img = new Image();\n                img.src = path;\n                img.onload = () => resolve(true);\n                img.onerror = () => resolve(false);\n            }));\n        return { isValidFileFormat, isValidUrl };\n    }\n\n    async onLoadUploadedUrl(url, resolve) {\n        const urlPathname = new URL(url, window.location.href).pathname;\n        const imageExtension = IMAGE_EXTENSIONS.find((format) => urlPathname.endsWith(format));\n        if (this.isImageField && imageExtension === \".webp\") {\n            // Do not allow the user to replace an image field by a\n            // webp CORS protected image as we are not currently\n            // able to manage the report creation if such images are\n            // in there (as the equivalent jpeg can not be\n            // generated). It also causes a problem for resize\n            // operations as 'libwep' can not be used.\n            this.notificationService.add(\n                _t(\n                    \"You can not replace a field by this image. If you want to use this image, first save it on your computer and then upload it here.\"\n                ),\n                {\n                    type: \"danger\",\n                    sticky: true,\n                }\n            );\n            return resolve();\n        }\n        super.onLoadUploadedUrl(url, resolve);\n    }\n\n    isInitialMedia(attachment) {\n        if (this.props.media.dataset.originalSrc) {\n            return this.props.media.dataset.originalSrc === attachment.image_src;\n        }\n        return this.props.media.getAttribute(\"src\") === attachment.image_src;\n    }\n\n    async fetchAttachments(limit, offset) {\n        const attachments = await super.fetchAttachments(limit, offset);\n        if (this.isImageField) {\n            // The image is a field; mark the attachments if they are linked to\n            // a webp CORS protected image. Indeed, in this case, they should\n            // not be selectable on the media dialog (due to a problem of image\n            // resize and report creation).\n            for (const attachment of attachments) {\n                if (\n                    attachment.mimetype === \"image/webp\" &&\n                    (await isSrcCorsProtected(attachment.image_src))\n                ) {\n                    attachment.unselectable = true;\n                }\n            }\n        }\n        // Color-substitution for dynamic SVG attachment\n        const primaryColors = {};\n        const htmlStyle = getHtmlStyle(document);\n        for (let color = 1; color <= 5; color++) {\n            primaryColors[color] = getCSSVariableValue(\"o-color-\" + color, htmlStyle);\n        }\n        return attachments.map((attachment) => {\n            if (attachment.image_src.startsWith(\"/\")) {\n                const newURL = new URL(attachment.image_src, window.location.origin);\n                // Set the main colors of dynamic SVGs to o-color-1~5\n                if (\n                    attachment.image_src.startsWith(\"/html_editor/shape/\") ||\n                    attachment.image_src.startsWith(\"/web_editor/shape/\")\n                ) {\n                    newURL.searchParams.forEach((value, key) => {\n                        const match = key.match(/^c([1-5])$/);\n                        if (match) {\n                            newURL.searchParams.set(key, primaryColors[match[1]]);\n                        }\n                    });\n                } else {\n                    // Set height so that db images load faster\n                    newURL.searchParams.set(\"height\", 2 * this.MIN_ROW_HEIGHT);\n                }\n                attachment.thumbnail_src = newURL.pathname + newURL.search;\n            }\n            if (this.selectInitialMedia() && this.isInitialMedia(attachment)) {\n                this.selectAttachment(attachment);\n            }\n            return attachment;\n        });\n    }\n\n    async fetchLibraryMedia(offset) {\n        if (!this.state.needle) {\n            return { media: [], results: null };\n        }\n\n        this.state.isFetchingLibrary = true;\n        try {\n            const response = await rpc(\n                \"/html_editor/media_library_search\",\n                {\n                    query: this.state.needle,\n                    offset: offset,\n                },\n                {\n                    silent: true,\n                }\n            );\n            this.state.isFetchingLibrary = false;\n            const media = (response.media || []).slice(0, this.NUMBER_OF_MEDIA_TO_DISPLAY);\n            media.forEach((record) => (record.mediaType = \"libraryMedia\"));\n            return { media, results: response.results };\n        } catch {\n            // Either API endpoint doesn't exist or is misconfigured.\n            console.error(`Couldn't reach API endpoint.`);\n            this.state.isFetchingLibrary = false;\n            return { media: [], results: null };\n        }\n    }\n\n    async loadMore(...args) {\n        await super.loadMore(...args);\n        if (\n            !this.props.useMediaLibrary ||\n            // The user can load more library media only when the filter is set.\n            this.state.searchService !== \"media-library\"\n        ) {\n            return;\n        }\n        return this.keepLastLibraryMedia\n            .add(this.fetchLibraryMedia(this.state.libraryMedia.length))\n            .then(({ media }) => {\n                // This is never reached if another search or loadMore occurred.\n                this.state.libraryMedia.push(...media);\n            });\n    }\n\n    async search(...args) {\n        await super.search(...args);\n        if (!this.props.useMediaLibrary) {\n            return;\n        }\n        if (!this.state.needle) {\n            this.state.searchService = \"all\";\n        }\n        this.state.libraryMedia = [];\n        this.state.libraryResults = 0;\n        return this.keepLastLibraryMedia\n            .add(this.fetchLibraryMedia(0))\n            .then(({ media, results }) => {\n                // This is never reached if a new search occurred.\n                this.state.libraryMedia = media;\n                this.state.libraryResults = results;\n            });\n    }\n\n    async onClickAttachment(attachment) {\n        if (attachment.unselectable) {\n            this.notificationService.add(\n                _t(\n                    \"You can not replace a field by this image. If you want to use this image, first save it on your computer and then upload it here.\"\n                ),\n                {\n                    type: \"danger\",\n                    sticky: true,\n                }\n            );\n            return;\n        }\n        this.selectAttachment(attachment);\n        if (!this.props.multiSelect) {\n            await this.props.save();\n        }\n    }\n\n    async onClickMedia(media) {\n        this.props.selectMedia({ ...media, mediaType: \"libraryMedia\" });\n        if (!this.props.multiSelect) {\n            await this.props.save();\n        }\n    }\n\n    /**\n     * Utility method used by the MediaDialog component.\n     */\n    static async createElements(selectedMedia, { orm }) {\n        // Create all media-library attachments.\n        const toSave = Object.fromEntries(\n            selectedMedia\n                .filter((media) => media.mediaType === \"libraryMedia\")\n                .map((media) => [\n                    media.id,\n                    {\n                        query: media.query || \"\",\n                        is_dynamic_svg: !!media.isDynamicSVG,\n                        dynamic_colors: media.dynamicColors,\n                    },\n                ])\n        );\n        let savedMedia = [];\n        if (Object.keys(toSave).length !== 0) {\n            savedMedia = await rpc(\"/html_editor/save_library_media\", { media: toSave });\n        }\n        const selected = selectedMedia\n            .filter((media) => media.mediaType === \"attachment\")\n            .concat(savedMedia)\n            .map((attachment) => {\n                // Color-customize dynamic SVGs with the theme colors\n                if (\n                    attachment.image_src &&\n                    (attachment.image_src.startsWith(\"/html_editor/shape/\") ||\n                        attachment.image_src.startsWith(\"/web_editor/shape/\"))\n                ) {\n                    const colorCustomizedURL = new URL(\n                        attachment.image_src,\n                        window.location.origin\n                    );\n                    const htmlStyle = getHtmlStyle(document);\n                    colorCustomizedURL.searchParams.forEach((value, key) => {\n                        const match = key.match(/^c([1-5])$/);\n                        if (match) {\n                            colorCustomizedURL.searchParams.set(\n                                key,\n                                getCSSVariableValue(`o-color-${match[1]}`, htmlStyle)\n                            );\n                        }\n                    });\n                    attachment.image_src = colorCustomizedURL.pathname + colorCustomizedURL.search;\n                }\n                return attachment;\n            });\n        return Promise.all(\n            selected.map(async (attachment) => {\n                const imageEl = document.createElement(\"img\");\n                let src = attachment.image_src;\n                if (!attachment.public && !attachment.url) {\n                    let accessToken = attachment.access_token;\n                    if (!accessToken) {\n                        [accessToken] = await orm.call(\"ir.attachment\", \"generate_access_token\", [\n                            attachment.id,\n                        ]);\n                    }\n                    src += `?access_token=${encodeURIComponent(accessToken)}`;\n                }\n                imageEl.src = src;\n                imageEl.alt = attachment.description || \"\";\n                imageEl.dataset.attachmentId = attachment.id;\n                return imageEl;\n            })\n        );\n    }\n\n    async onImageLoaded(imgEl, attachment) {\n        this.debouncedScrollUpdate();\n        if (attachment.mediaType === \"libraryMedia\" && !imgEl.src.startsWith(\"blob\")) {\n            // This call applies the theme's color palette to the\n            // loaded illustration. Upon replacement of the image,\n            // `onImageLoad` is called again, but the replacement image\n            // has an URL that starts with 'blob'. The condition above\n            // uses this to avoid an infinite loop.\n            await this.onLibraryImageLoaded(imgEl, attachment);\n        }\n    }\n\n    /**\n     * This converts the colors of an svg coming from the media library to\n     * the palette's ones, and make them dynamic.\n     *\n     * @param {HTMLElement} imgEl\n     * @param {Object} media\n     * @returns\n     */\n    async onLibraryImageLoaded(imgEl, media) {\n        const mediaUrl = imgEl.src;\n        try {\n            const response = await fetch(mediaUrl);\n            if (response.headers.get(\"content-type\").startsWith(\"image/svg+xml\")) {\n                let svg = await response.text();\n                const dynamicColors = {};\n                const combinedColorsRegex = new RegExp(\n                    Object.values(DEFAULT_PALETTE).join(\"|\"),\n                    \"gi\"\n                );\n                const htmlStyle = getHtmlStyle(document);\n                svg = svg.replace(combinedColorsRegex, (match) => {\n                    const colorId = Object.keys(DEFAULT_PALETTE).find(\n                        (key) => DEFAULT_PALETTE[key] === match.toUpperCase()\n                    );\n                    const colorKey = \"c\" + colorId;\n                    dynamicColors[colorKey] = getCSSVariableValue(\"o-color-\" + colorId, htmlStyle);\n                    return dynamicColors[colorKey];\n                });\n                const fileName = mediaUrl.split(\"/\").pop();\n                const file = new File([svg], fileName, {\n                    type: \"image/svg+xml\",\n                });\n                imgEl.src = URL.createObjectURL(file);\n                if (Object.keys(dynamicColors).length) {\n                    media.isDynamicSVG = true;\n                    media.dynamicColors = dynamicColors;\n                }\n            }\n        } catch {\n            console.error(\n                \"CORS is misconfigured on the API server, image will be treated as non-dynamic.\"\n            );\n        }\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { useService, useChildRef } from \"@web/core/utils/hooks\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { Notebook } from \"@web/core/notebook/notebook\";\nimport { ImageSelector } from \"./image_selector\";\nimport { IconSelector } from \"./icon_selector\";\n\nimport { Component, useState, useRef, useEffect } from \"@odoo/owl\";\nimport { iconClasses } from \"@html_editor/utils/dom_info\";\n\nexport const TABS = {\n    IMAGES: {\n        id: \"IMAGES\",\n        title: _t(\"Images\"),\n        Component: ImageSelector,\n        sequence: 10,\n    },\n    ICONS: {\n        id: \"ICONS\",\n        title: _t(\"Icons\"),\n        Component: IconSelector,\n        sequence: 20,\n    },\n};\n\nconst DEFAULT_SEQUENCE = 50;\nconst sequence = (tab) => tab.sequence ?? DEFAULT_SEQUENCE;\n\nexport class MediaDialog extends Component {\n    static template = \"html_editor.MediaDialog\";\n    static defaultProps = {\n        useMediaLibrary: true,\n        extraTabs: [],\n    };\n    static components = {\n        Dialog,\n        Notebook,\n    };\n    static props = {\n        extraTabs: { type: Array, optional: true, element: Object },\n        visibleTabs: { type: Array, optional: true, element: String },\n        activeTab: { type: String, optional: true },\n        \"*\": true,\n    };\n\n    setup() {\n        this.size = \"xl\";\n        this.contentClass = \"o_select_media_dialog h-100\";\n        this.title = _t(\"Select a media\");\n        this.modalRef = useChildRef();\n\n        this.orm = useService(\"orm\");\n        this.notificationService = useService(\"notification\");\n\n        this.selectedMedia = useState({});\n\n        this.addButtonRef = useRef(\"add-button\");\n\n        this.initialIconClasses = [];\n\n        this.notebookPages = [];\n        this.addTabs();\n        this.notebookPages.sort((a, b) => sequence(a) - sequence(b));\n        this.tabs = Object.fromEntries(this.notebookPages.map((tab) => [tab.id, tab]));\n\n        this.errorMessages = {};\n\n        this.state = useState({\n            activeTab: this.initialActiveTab,\n            isSaving: false,\n        });\n\n        useEffect(\n            (nbSelectedAttachments) => {\n                // Disable/enable the add button depending on whether some media\n                // are selected or not.\n                this.addButtonRef.el.toggleAttribute(\n                    \"disabled\",\n                    !nbSelectedAttachments || this.state.isSaving\n                );\n            },\n            () => [this.selectedMedia[this.state.activeTab].length, this.state.isSaving]\n        );\n    }\n\n    get initialActiveTab() {\n        if (this.props.activeTab) {\n            return this.props.activeTab;\n        }\n        if (this.props.media) {\n            const correspondingTab = Object.keys(this.tabs).find((id) =>\n                this.tabs[id].Component.tagNames.includes(this.props.media.tagName)\n            );\n            if (correspondingTab) {\n                return correspondingTab;\n            }\n        }\n        return this.notebookPages[0].id;\n    }\n\n    addTab(tab, additionalProps = {}) {\n        if (this.props.visibleTabs && !this.props.visibleTabs.includes(tab.id)) {\n            return;\n        }\n        this.selectedMedia[tab.id] = [];\n        this.notebookPages.push({\n            ...tab,\n            props: {\n                ...tab.props,\n                ...additionalProps,\n                id: tab.id,\n                resModel: this.props.resModel,\n                resId: this.props.resId,\n                media: this.props.media,\n                // multiImages: this.props.multiImages,\n                selectedMedia: this.selectedMedia,\n                selectMedia: (...args) =>\n                    this.selectMedia(...args, tab.id, additionalProps.multiSelect),\n                save: this.save.bind(this),\n                onAttachmentChange: this.props.onAttachmentChange,\n                errorMessages: (errorMessage) => (this.errorMessages[tab.id] = errorMessage),\n                modalRef: this.modalRef,\n            },\n        });\n    }\n\n    addTabs() {\n        const onlyImages =\n            this.props.onlyImages ||\n            (this.props.media &&\n                this.props.media.parentElement &&\n                (this.props.media.parentElement.dataset.oeField === \"image\" ||\n                    this.props.media.parentElement.dataset.oeType === \"image\"));\n\n        if (!this.props.noImages) {\n            this.addTab(TABS.IMAGES, {\n                useMediaLibrary: this.props.useMediaLibrary,\n                multiSelect: this.props.multiImages,\n                addFieldImage: this.props.addFieldImage,\n            });\n        }\n        if (onlyImages) {\n            return;\n        }\n        const addIcons = !this.props.visibleTabs || this.props.visibleTabs.includes(TABS.ICONS.id);\n        if (addIcons) {\n            const fonts = TABS.ICONS.Component.initFonts();\n            this.addTab(TABS.ICONS, {\n                fonts,\n            });\n\n            if (\n                this.props.media &&\n                TABS.ICONS.Component.tagNames.includes(this.props.media.tagName)\n            ) {\n                const classes = this.props.media.className.split(/\\s+/);\n                const predefinedMediaFont = fonts.find((font) => classes.includes(font.base));\n                if (predefinedMediaFont) {\n                    const selectedIcon = predefinedMediaFont.icons.find((icon) =>\n                        icon.names.some((name) => classes.includes(name))\n                    );\n                    if (selectedIcon) {\n                        this.initialIconClasses.push(...selectedIcon.names);\n                        this.selectMedia(selectedIcon, TABS.ICONS.id);\n                    }\n                } else {\n                    const iconRegex = new RegExp(`\\\\b(?:${iconClasses.join(\"|\")})(?:-\\\\S+)?\\\\b`);\n                    const fallbackIconClasses = classes.filter((cls) => iconRegex.test(cls));\n                    this.initialIconClasses.push(...fallbackIconClasses);\n                }\n            }\n        }\n        this.props.extraTabs.forEach((tab) => this.addTab(tab));\n    }\n\n    /**\n     * Render the selected media for insertion in the editor\n     *\n     * @param {Array<Object>} selectedMedia\n     * @returns {Array<HTMLElement>}\n     */\n    async renderMedia(selectedMedia) {\n        const elements = await this.tabs[this.state.activeTab].Component.createElements(\n            selectedMedia,\n            { orm: this.orm }\n        );\n        elements.forEach((element) => {\n            if (this.props.media) {\n                element.classList.add(...this.props.media.classList);\n                const style = this.props.media.getAttribute(\"style\");\n                if (style) {\n                    element.setAttribute(\"style\", style);\n                }\n                if (this.state.activeTab === TABS.IMAGES.id) {\n                    if (this.props.media.dataset.shape) {\n                        element.dataset.shape = this.props.media.dataset.shape;\n                    }\n                    if (this.props.media.dataset.shapeColors) {\n                        element.dataset.shapeColors = this.props.media.dataset.shapeColors;\n                    }\n                    if (this.props.media.dataset.shapeFlip) {\n                        element.dataset.shapeFlip = this.props.media.dataset.shapeFlip;\n                    }\n                    if (this.props.media.dataset.shapeRotate) {\n                        element.dataset.shapeRotate = this.props.media.dataset.shapeRotate;\n                    }\n                    if (this.props.media.dataset.hoverEffect) {\n                        element.dataset.hoverEffect = this.props.media.dataset.hoverEffect;\n                    }\n                    if (this.props.media.dataset.hoverEffectColor) {\n                        element.dataset.hoverEffectColor =\n                            this.props.media.dataset.hoverEffectColor;\n                    }\n                    if (this.props.media.dataset.hoverEffectStrokeWidth) {\n                        element.dataset.hoverEffectStrokeWidth =\n                            this.props.media.dataset.hoverEffectStrokeWidth;\n                    }\n                    if (this.props.media.dataset.hoverEffectIntensity) {\n                        element.dataset.hoverEffectIntensity =\n                            this.props.media.dataset.hoverEffectIntensity;\n                    }\n                }\n            }\n            for (const otherTab of Object.keys(this.tabs).filter(\n                (key) => key !== this.state.activeTab\n            )) {\n                for (const property of this.tabs[otherTab].Component.mediaSpecificStyles) {\n                    element.style.removeProperty(property);\n                }\n                element.classList.remove(...this.tabs[otherTab].Component.mediaSpecificClasses);\n                const extraClassesToRemove = [];\n                for (const name of this.tabs[otherTab].Component.mediaExtraClasses) {\n                    if (typeof name === \"string\") {\n                        extraClassesToRemove.push(name);\n                    } else {\n                        // Regex\n                        for (const className of element.classList) {\n                            if (className.match(name)) {\n                                extraClassesToRemove.push(className);\n                            }\n                        }\n                    }\n                }\n                // Remove classes that do not also exist in the target type.\n                element.classList.remove(\n                    ...extraClassesToRemove.filter((candidateName) => {\n                        for (const name of this.tabs[this.state.activeTab].Component\n                            .mediaExtraClasses) {\n                            if (typeof name === \"string\") {\n                                if (candidateName === name) {\n                                    return false;\n                                }\n                            } else {\n                                // Regex\n                                if (candidateName.match(name)) {\n                                    return false;\n                                }\n                            }\n                        }\n                        return true;\n                    })\n                );\n            }\n            element.classList.remove(...this.initialIconClasses);\n            element.classList.remove(\"o_modified_image_to_save\");\n            element.classList.remove(\"oe_edited_link\");\n            element.classList.add(\n                ...this.tabs[this.state.activeTab].Component.mediaSpecificClasses\n            );\n        });\n        return elements;\n    }\n\n    selectMedia(media, tabId, multiSelect) {\n        if (media && !Object.keys(media).length) {\n            // Clear media selection when an empty object is passed\n            this.selectedMedia[tabId] = [];\n            return;\n        }\n        if (multiSelect) {\n            const isMediaSelected = this.selectedMedia[tabId]\n                .map(({ id }) => id)\n                .includes(media.id);\n            if (!isMediaSelected) {\n                this.selectedMedia[tabId].push(media);\n            } else {\n                this.selectedMedia[tabId] = this.selectedMedia[tabId].filter(\n                    (m) => m.id !== media.id\n                );\n            }\n        } else {\n            this.selectedMedia[tabId] = [media];\n        }\n    }\n\n    async save() {\n        if (this.errorMessages[this.state.activeTab]) {\n            this.notificationService.add(this.errorMessages[this.state.activeTab], {\n                type: \"danger\",\n            });\n            return;\n        }\n        const selectedMedia = this.selectedMedia[this.state.activeTab];\n        // TODO In master: clean the save method so it performs the specific\n        // adaptation before saving from the active media selector and find a\n        // way to simply close the dialog if the media element remains the same.\n        const saveSelectedMedia =\n            selectedMedia.length &&\n            (this.state.activeTab !== TABS.ICONS.id ||\n                selectedMedia[0].initialIconChanged ||\n                !this.props.media);\n        this.state.isSaving = true;\n        if (saveSelectedMedia) {\n            const elements = await this.renderMedia(selectedMedia);\n            if (this.props.multiImages) {\n                await this.props.save(elements, selectedMedia, this.state.activeTab);\n            } else {\n                await this.props.save(elements[0], selectedMedia, this.state.activeTab);\n            }\n        }\n        this.props.close();\n        this.state.isSaving = false;\n    }\n\n    onTabChange(tab) {\n        this.state.activeTab = tab;\n    }\n}\n", "import { useDebounced } from \"@web/core/utils/timing\";\nimport { useAutofocus } from \"@web/core/utils/hooks\";\n\nimport { Component, useEffect, useState } from \"@odoo/owl\";\n\nexport class SearchMedia extends Component {\n    static template = \"html_editor.SearchMedia\";\n    static props = [\"searchPlaceholder\", \"search\", \"needle\"];\n    setup() {\n        useAutofocus({ mobile: true });\n        this.debouncedSearch = useDebounced(this.props.search, 1000);\n\n        this.state = useState({\n            input: this.props.needle || \"\",\n        });\n\n        useEffect(\n            (input) => {\n                // Do not trigger a search on the initial render.\n                if (this.hasRendered) {\n                    this.debouncedSearch(input);\n                } else {\n                    this.hasRendered = true;\n                }\n            },\n            () => [this.state.input]\n        );\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { Component, useState } from \"@odoo/owl\";\n\nexport class ProgressBar extends Component {\n    static template = \"html_editor.ProgressBar\";\n    static props = {\n        progress: { type: Number, optional: true },\n        hasError: { type: Boolean, optional: true },\n        uploaded: { type: Boolean, optional: true },\n        name: String,\n        size: { type: String, optional: true },\n        errorMessage: { type: String, optional: true },\n    };\n    static defaultProps = {\n        progress: 0,\n        hasError: false,\n        uploaded: false,\n        size: \"\",\n        errorMessage: \"\",\n    };\n\n    get errorMessage() {\n        return this.props.errorMessage || _t(\"File could not be saved\");\n    }\n\n    get progress() {\n        return Math.round(this.props.progress);\n    }\n}\n\nexport class UploadProgressToast extends Component {\n    static template = \"html_editor.UploadProgressToast\";\n    static components = {\n        ProgressBar,\n    };\n    static props = {\n        close: Function,\n    };\n\n    setup() {\n        this.uploadService = useService(\"upload\");\n        this.state = useState(this.uploadService.progressToast);\n    }\n}\n", "import { rpc } from \"@web/core/network/rpc\";\nimport { registry } from \"@web/core/registry\";\nimport { UploadProgressToast } from \"./upload_progress_toast\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { checkFileSize } from \"@web/core/utils/files\";\nimport { humanNumber } from \"@web/core/utils/numbers\";\nimport { getDataURLFromFile } from \"@web/core/utils/urls\";\nimport { reactive } from \"@odoo/owl\";\n\nexport const AUTOCLOSE_DELAY = 3000;\nexport const AUTOCLOSE_DELAY_LONG = 8000;\n\nexport const uploadService = {\n    dependencies: [\"notification\"],\n    start(env, { notification }) {\n        let fileId = 0;\n        const progressToast = reactive({\n            files: {},\n            isVisible: false,\n        });\n\n        registry.category(\"main_components\").add(\"UploadProgressToast\", {\n            Component: UploadProgressToast,\n            props: {\n                close: () => (progressToast.isVisible = false),\n            },\n        });\n\n        const addFile = (file) => {\n            progressToast.files[file.id] = file;\n            progressToast.isVisible = true;\n            return progressToast.files[file.id];\n        };\n\n        const deleteFile = (fileId) => {\n            delete progressToast.files[fileId];\n            if (!Object.keys(progressToast.files).length) {\n                progressToast.isVisible = false;\n            }\n        };\n        return {\n            get progressToast() {\n                return progressToast;\n            },\n            get fileId() {\n                return fileId;\n            },\n            addFile,\n            deleteFile,\n            incrementId() {\n                fileId++;\n            },\n            uploadUrl: async (url, { resModel, resId }, onUploaded) => {\n                const attachment = await rpc(\"/html_editor/attachment/add_url\", {\n                    url,\n                    res_model: resModel,\n                    res_id: resId,\n                });\n                await onUploaded(attachment);\n            },\n            /**\n             * This takes an array of files (from an input HTMLElement), and\n             * uploads them while managing the UploadProgressToast.\n             *\n             * @param {Array<File>} files\n             * @param {Object} options\n             * @param {Function} onUploaded\n             */\n            uploadFiles: async (files, { resModel, resId, isImage }, onUploaded) => {\n                // Upload the smallest file first to block the user the least possible.\n                const sortedFiles = Array.from(files).sort((a, b) => a.size - b.size);\n                for (const file of sortedFiles) {\n                    let fileSize = file.size;\n                    if (!checkFileSize(fileSize, notification)) {\n                        return null;\n                    }\n                    if (!fileSize) {\n                        fileSize = \"\";\n                    } else {\n                        fileSize = humanNumber(fileSize) + \"B\";\n                    }\n\n                    const id = ++fileId;\n                    file.progressToastId = id;\n                    // This reactive object, built based on the files array,\n                    // is given as a prop to the UploadProgressToast.\n                    addFile({\n                        id,\n                        name: file.name,\n                        size: fileSize,\n                    });\n                }\n\n                // Upload one file at a time: no need to parallel as upload is\n                // limited by bandwidth.\n                for (const sortedFile of sortedFiles) {\n                    const file = progressToast.files[sortedFile.progressToastId];\n                    let dataURL;\n                    try {\n                        dataURL = await getDataURLFromFile(sortedFile);\n                    } catch {\n                        deleteFile(file.id);\n                        env.services.notification.add(\n                            _t('Could not load the file \"%s\".', sortedFile.name),\n                            { type: \"danger\" }\n                        );\n                        continue;\n                    }\n                    try {\n                        const xhr = new XMLHttpRequest();\n                        xhr.upload.addEventListener(\"progress\", (ev) => {\n                            const rpcComplete = (ev.loaded / ev.total) * 100;\n                            file.progress = rpcComplete;\n                        });\n                        xhr.upload.addEventListener(\"load\", function () {\n                            // Don't show yet success as backend code only starts now\n                            file.progress = 100;\n                        });\n                        const attachment = await rpc(\n                            \"/html_editor/attachment/add_data\",\n                            {\n                                name: file.name,\n                                data: dataURL.split(\",\")[1],\n                                res_id: resId,\n                                res_model: resModel,\n                                is_image: !!isImage,\n                                width: 0,\n                                quality: 0,\n                            },\n                            { xhr }\n                        );\n                        if (attachment.error) {\n                            file.hasError = true;\n                            file.errorMessage = attachment.error;\n                        } else {\n                            if (attachment.mimetype === \"image/webp\") {\n                                // Generate alternate format for reports.\n                                const image = document.createElement(\"img\");\n                                image.src = `data:image/webp;base64,${dataURL.split(\",\")[1]}`;\n                                await new Promise((resolve) =>\n                                    image.addEventListener(\"load\", resolve)\n                                );\n                                const canvas = document.createElement(\"canvas\");\n                                canvas.width = image.width;\n                                canvas.height = image.height;\n                                const ctx = canvas.getContext(\"2d\");\n                                ctx.fillStyle = \"rgb(255, 255, 255)\";\n                                ctx.fillRect(0, 0, canvas.width, canvas.height);\n                                ctx.drawImage(image, 0, 0);\n                                const altDataURL = canvas.toDataURL(\"image/jpeg\");\n                                await rpc(\n                                    \"/html_editor/attachment/add_data\",\n                                    {\n                                        name: file.name.replace(/\\.webp$/, \".jpg\"),\n                                        data: altDataURL.split(\",\")[1],\n                                        res_id: attachment.id,\n                                        res_model: \"ir.attachment\",\n                                        is_image: true,\n                                        width: 0,\n                                        quality: 0,\n                                    },\n                                    { xhr }\n                                );\n                            }\n                            file.uploaded = true;\n                            await onUploaded(attachment);\n                        }\n                        // If there's an error, display the error message for longer\n                        const message_autoclose_delay = file.hasError\n                            ? AUTOCLOSE_DELAY_LONG\n                            : AUTOCLOSE_DELAY;\n                        setTimeout(() => deleteFile(file.id), message_autoclose_delay);\n                    } catch (error) {\n                        file.hasError = true;\n                        setTimeout(() => deleteFile(file.id), AUTOCLOSE_DELAY_LONG);\n                        throw error;\n                    }\n                }\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"upload\", uploadService);\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useAutofocus, useService } from \"@web/core/utils/hooks\";\nimport { debounce } from \"@web/core/utils/timing\";\n\nimport { Component, useState, useRef, onMounted, status } from \"@odoo/owl\";\nimport { Switch } from \"@html_editor/components/switch/switch\";\n\nclass VideoOption extends Component {\n    static template = \"html_editor.VideoOption\";\n    static components = {\n        Switch,\n    };\n    static props = {\n        description: { type: String, optional: true },\n        label: { type: String, optional: true },\n        onChangeOption: Function,\n        onChangeStartAt: Function,\n        value: { type: String, optional: true },\n        name: { type: String, optional: true },\n    };\n\n    get showStartAtInput() {\n        return this.props.name === \"start_from\";\n    }\n}\n\nclass VideoIframe extends Component {\n    static template = \"html_editor.VideoIframe\";\n    static props = {\n        src: { type: String },\n    };\n}\n\nexport class VideoSelector extends Component {\n    static mediaSpecificClasses = [\"media_iframe_video\"];\n    static mediaSpecificStyles = [];\n    static mediaExtraClasses = [];\n    static tagNames = [\"IFRAME\", \"DIV\"];\n    static template = \"html_editor.VideoSelector\";\n    static components = {\n        VideoIframe,\n        VideoOption,\n    };\n    static props = {\n        selectMedia: Function,\n        errorMessages: Function,\n        vimeoPreviewIds: { type: Array, optional: true },\n        isForBgVideo: { type: Boolean, optional: true },\n        media: { validate: (p) => p.nodeType === Node.ELEMENT_NODE, optional: true },\n        \"*\": true,\n    };\n    static defaultProps = {\n        vimeoPreviewIds: [],\n        isForBgVideo: false,\n    };\n\n    setup() {\n        this.http = useService(\"http\");\n\n        this.state = useState({\n            options: [],\n            src: \"\",\n            urlInput: \"\",\n            platform: null,\n            vimeoPreviews: [],\n            errorMessage: \"\",\n        });\n\n        this.PLATFORMS = {\n            youtube: \"youtube\",\n            dailymotion: \"dailymotion\",\n            vimeo: \"vimeo\",\n        };\n\n        this.platformParams = {\n            youtube: \"start\",\n            dailymotion: \"startTime\",\n            vimeo: \"#t=\",\n        };\n\n        this.OPTIONS = {\n            autoplay: {\n                label: _t(\"Autoplay\"),\n                description: _t(\"Videos are muted when autoplay is enabled\"),\n                platforms: [\n                    this.PLATFORMS.youtube,\n                    this.PLATFORMS.vimeo,\n                ],\n                urlParameter: () => \"autoplay=1\",\n            },\n            loop: {\n                label: _t(\"Loop\"),\n                platforms: [this.PLATFORMS.youtube, this.PLATFORMS.vimeo],\n                urlParameter: () => \"loop=1\",\n            },\n            hide_controls: {\n                label: _t(\"Hide player controls\"),\n                platforms: [\n                    this.PLATFORMS.youtube,\n                    this.PLATFORMS.vimeo,\n                ],\n                urlParameter: () => \"controls=0\",\n            },\n            hide_fullscreen: {\n                label: _t(\"Hide fullscreen button\"),\n                platforms: [this.PLATFORMS.youtube],\n                urlParameter: () => \"fs=0\",\n                isHidden: () =>\n                    this.state.options.filter((option) => option.id === \"hide_controls\")[0].value,\n            },\n            start_from: {\n                label: _t(\"Start at\"),\n                platforms: [\n                    this.PLATFORMS.youtube,\n                    this.PLATFORMS.vimeo,\n                    this.PLATFORMS.dailymotion,\n                ],\n                urlParameter: () => this.platformParams[this.state.platform],\n            },\n        };\n        this.urlInputRef = useRef(\"url-input\");\n\n        onMounted(async () => {\n            if (this.props.media) {\n                const src =\n                    this.props.media.dataset.oeExpression ||\n                    this.props.media.dataset.src ||\n                    (this.props.media.tagName === \"IFRAME\" &&\n                        this.props.media.getAttribute(\"src\")) ||\n                    \"\";\n                if (src) {\n                    this.state.urlInput = src;\n                    if (!src.startsWith(\"https:\") && !src.startsWith(\"http:\")) {\n                        this.state.urlInput = \"https:\" + this.state.urlInput;\n                    }\n                    await this.syncOptionsWithUrl();\n                    if (status(this) === \"destroyed\") {\n                        return;\n                    }\n                }\n            }\n            await this.prepareVimeoPreviews();\n        });\n\n        useAutofocus();\n\n        this.onChangeUrl = debounce(() => this.syncOptionsWithUrl(), 500);\n\n        this.onChangeStartAt = debounce(async (ev, optionId) => {\n            const start_from = this.convertTimestampToSeconds(ev.target.value);\n            this.state.options = this.state.options.map((option) => {\n                if (option.id === optionId) {\n                    // to avoid showing \"0\" when seconds are 0, we set it to \"00:00\"\n                    return { ...option, value: start_from === \"0\" ? \"00:00\" : start_from };\n                }\n                return option;\n            });\n            await this.updateVideo();\n            this.state.urlInput = \"https:\" + this.state.src;\n        }, 1000);\n    }\n\n    get shownOptions() {\n        if (this.props.isForBgVideo) {\n            return [];\n        }\n        return this.state.options.filter(\n            (option) => !this.OPTIONS[option.id].isHidden || !this.OPTIONS[option.id].isHidden()\n        );\n    }\n\n    get value() {\n        if (this.option.id === \"start_from\" && this.option.value !== \"00:00\") {\n            return this.convertSecondsToTimestamp(this.option.value);\n        }\n        return this.option.value;\n    }\n\n    async onChangeOption(optionId) {\n        this.state.options = this.state.options.map((option) => {\n            if (option.id === optionId) {\n                // used \"0\" here, to set the initial \"startAt\" value if option is toggled on,\n                // for other option it works as truthy value.\n                return { ...option, value: !option.value && \"00:00\" };\n            }\n            return option;\n        });\n        await this.updateVideo();\n        this.state.urlInput = \"https:\" + this.state.src;\n    }\n\n    async onClickSuggestion(src) {\n        this.state.urlInput = src;\n        await this.updateVideo();\n    }\n\n    async updateVideo() {\n        if (!this.state.urlInput) {\n            this.state.src = \"\";\n            this.state.urlInput = \"\";\n            this.state.options = [];\n            this.state.platform = null;\n            this.state.errorMessage = \"\";\n            /**\n             * When the url input is emptied, we need to call the `selectMedia`\n             * callback function to notify the other components that the media\n             * has changed.\n             */\n            this.props.selectMedia({});\n            return;\n        }\n\n        // Detect if we have an embed code rather than an URL\n        const embedMatch = this.state.urlInput.match(/(src|href)=[\"']?([^\"']+)?/);\n        if (embedMatch && embedMatch[2].length > 0 && embedMatch[2].indexOf(\"instagram\")) {\n            embedMatch[1] = embedMatch[2]; // Instagram embed code is different\n        }\n        const url = embedMatch ? embedMatch[1] : this.state.urlInput;\n\n        const options = {};\n        if (this.props.isForBgVideo && URL.canParse(url)) {\n            const parsedUrl = new URL(url);\n            const urlParams = parsedUrl.searchParams;\n            const startFrom =\n                urlParams.get(\"start\") || urlParams.get(\"startTime\") || urlParams.get(\"t\");\n            Object.keys(this.OPTIONS).forEach((key) => {\n                options[key] = key === \"start_from\" ? startFrom : true;\n            });\n        } else {\n            for (const option of this.shownOptions) {\n                options[option.id] = option.value;\n            }\n        }\n\n        const {\n            embed_url: src,\n            video_id: videoId,\n            params,\n            platform,\n        } = await this._getVideoURLData(url, options);\n\n        if (!src) {\n            this.state.errorMessage = _t(\"The provided url is not valid\");\n        } else if (!platform) {\n            this.state.errorMessage = _t(\"The provided url does not reference any supported video\");\n        } else {\n            this.state.errorMessage = \"\";\n        }\n        this.props.errorMessages(this.state.errorMessage);\n\n        const newOptions = [];\n        if (platform && platform !== this.state.platform) {\n            Object.keys(this.OPTIONS).forEach((key) => {\n                if (this.OPTIONS[key].platforms.includes(platform)) {\n                    const { label, description } = this.OPTIONS[key];\n                    newOptions.push({ id: key, label, description });\n                }\n            });\n        }\n\n        this.state.src = src;\n        this.props.selectMedia({\n            id: src,\n            src,\n            platform,\n            videoId,\n            params,\n        });\n        if (platform !== this.state.platform) {\n            this.state.platform = platform;\n            this.state.options = newOptions;\n        }\n    }\n\n    /**\n     * Keep rpc call in distinct method make it patchable by test.\n     */\n    async _getVideoURLData(url, options) {\n        return await rpc(\"/html_editor/video_url/data\", {\n            video_url: url,\n            ...options,\n        });\n    }\n\n    /**\n     * Utility method, called by the MediaDialog component.\n     */\n    static createElements(selectedMedia) {\n        return selectedMedia.map((video) => {\n            const div = document.createElement(\"div\");\n            div.dataset.oeExpression = video.src;\n            div.innerHTML =\n                '<div class=\"css_editable_mode_display\"></div>' +\n                '<div class=\"media_iframe_video_size\" contenteditable=\"false\"></div>' +\n                '<iframe frameborder=\"0\" contenteditable=\"false\" allowfullscreen=\"allowfullscreen\"></iframe>';\n\n            div.querySelector(\"iframe\").src = video.src;\n            return div;\n        });\n    }\n\n    /**\n     * Based on the config vimeo ids, prepare the vimeo previews.\n     */\n    async prepareVimeoPreviews() {\n        await Promise.all(\n            this.props.vimeoPreviewIds.map(async (videoId) => {\n                const { thumbnail_url: thumbnailSrc } = await this.http.get(\n                    `https://vimeo.com/api/oembed.json?url=http%3A//vimeo.com/${encodeURIComponent(\n                        videoId\n                    )}`\n                );\n                this.state.vimeoPreviews.push({\n                    id: videoId,\n                    thumbnailSrc,\n                    src: `https://player.vimeo.com/video/${encodeURIComponent(videoId)}`,\n                });\n            })\n        );\n    }\n\n    /**\n     * Utility method to make options and urlInput state consistent with state of component.\n     */\n    async syncOptionsWithUrl() {\n        await this.updateVideo();\n        if (!URL.canParse(this.state.urlInput)) {\n            return;\n        }\n        const parsedUrl = new URL(this.state.urlInput);\n        const urlParams = parsedUrl.searchParams;\n        this.state.options = this.state.options.map((option) => {\n            const urlParameter = this.OPTIONS[option.id].urlParameter();\n            let value = \"\";\n\n            switch (urlParameter) {\n                case \"#t=\":\n                    value = this.parseTimeToSeconds(this.state.urlInput.split(\"#t=\")[1]);\n                    break;\n                case \"start\":\n                    value = urlParams.get(\"start\") || urlParams.get(\"t\");\n                    break;\n                case \"startTime\":\n                    value = urlParams.get(\"startTime\") || urlParams.get(\"start\");\n                    break;\n                default:\n                    value = this.state.urlInput.includes(urlParameter);\n            }\n            if (option.id === \"start_from\" && value === \"0\") {\n                return { ...option, value: \"00:00\" };\n            }\n            return { ...option, value: value || \"\" };\n        });\n        await this.updateVideo();\n    }\n\n    /**\n     * Utility method,to convert timestamp to seconds.\n     *\n     * @param {string} timestamp - The start time in HH:MM:SS format or seconds.\n     * @returns {string} - The start time in seconds.\n     */\n    convertTimestampToSeconds(timestamp) {\n        timestamp = timestamp.trim();\n        // Regular expression for HH:MM:SS format\n        const timeRegex = /^(?:(\\d+):)?([0-5]?\\d):([0-5]?\\d)$/;\n        if (timeRegex.test(timestamp)) {\n            return (timestamp =\n                timestamp.split(\":\").reduce((acc, time) => acc * 60 + +time, 0) + \"\");\n        }\n        if (isNaN(timestamp)) {\n            return \"0\";\n        }\n        return timestamp;\n    }\n    /**\n     * Utility method,to convert seconds to timestamp.\n     *\n     * @param {string} value - The start time in seconds.\n     * @returns {string} - The start time in HH:MM:SS or MM:SS format.\n     */\n    convertSecondsToTimestamp(value) {\n        if (!value) {\n            return \"\";\n        }\n        const match = value.match(/^\\d+s?$/);\n        if (!match) {\n            return \"\";\n        }\n\n        const totalSeconds = parseInt(match[0], 10);\n        if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) {\n            return value;\n        }\n        const hours = Math.floor(totalSeconds / 3600);\n        const minutes = Math.floor((totalSeconds % 3600) / 60);\n        const seconds = totalSeconds % 60;\n        const pad = (n) => String(n).padStart(2, \"0\");\n\n        if (hours > 0) {\n            return `${hours}:${pad(minutes)}:${pad(seconds)}`;\n        }\n        return `${minutes}:${pad(seconds)}`;\n    }\n    /**\n     * Utility method,to convert 'XmYs', Xm, Ys to seconds for vimeo platform.\n     *\n     * @param {string} value - The start time in 'XmYs' type format.\n     * @returns {string} - The start time in seconds.\n     */\n    parseTimeToSeconds(value) {\n        const match = value?.match(/^(?:(\\d+)m(\\d+)s|(\\d+)m|(\\d+)s|(\\d+))$/);\n        if (!match) {\n            return value;\n        }\n        let minutes = match[1] || match[3];\n        minutes = parseInt(minutes || \"0\", 10);\n        let seconds = match[2] || match[4] || match[5];\n        seconds = parseInt(seconds || \"0\", 10);\n        return String(minutes * 60 + seconds);\n    }\n}\n", "import { patch } from \"@web/core/utils/patch\";\nimport { ImageSelector as HtmlImageSelector } from \"@html_editor/main/media/media_dialog/image_selector\";\n\npatch(HtmlImageSelector.prototype, {\n    get attachmentsDomain() {\n        const domain = super.attachmentsDomain;\n        domain.push(\"|\", [\"url\", \"=\", false], \"!\", [\"url\", \"=like\", \"/web/image/website.%\"]);\n        domain.push([\"key\", \"=\", false]);\n        return domain;\n    },\n});\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { ImageSelector } from \"@html_editor/main/media/media_dialog/image_selector\";\n\nimport { UnsplashError } from \"../unsplash_error/unsplash_error\";\nimport { useState } from \"@odoo/owl\";\n\npatch(ImageSelector.prototype, {\n    setup() {\n        super.setup();\n        this.unsplash = useService(\"unsplash\");\n        this.keepLastUnsplash = new KeepLast();\n        this.unsplashState = useState({\n            unsplashRecords: [],\n            isFetchingUnsplash: false,\n            isMaxed: false,\n            unsplashError: null,\n            useUnsplash: true,\n        });\n\n        this.NUMBER_OF_RECORDS_TO_DISPLAY = 30;\n\n        this.errorMessages = {\n            key_not_found: {\n                title: _t(\"Setup Unsplash to access royalty free photos.\"),\n                subtitle: \"\",\n            },\n            401: {\n                title: _t(\"Unauthorized Key\"),\n                subtitle: _t(\"Please check your Unsplash access key and application ID.\"),\n            },\n            403: {\n                title: _t(\"Search is temporarily unavailable\"),\n                subtitle: _t(\n                    \"The max number of searches is exceeded. Please retry in an hour or extend to a better account.\"\n                ),\n            },\n        };\n    },\n\n    get canLoadMore() {\n        if (this.state.searchService === \"all\") {\n            return (\n                super.canLoadMore ||\n                (this.state.needle &&\n                    !this.unsplashState.isMaxed &&\n                    !this.unsplashState.unsplashError)\n            );\n        } else if (this.state.searchService === \"unsplash\") {\n            return (\n                this.state.needle &&\n                !this.unsplashState.isMaxed &&\n                !this.unsplashState.unsplashError\n            );\n        }\n        return super.canLoadMore;\n    },\n\n    get hasContent() {\n        if (this.state.searchService === \"all\") {\n            return super.hasContent || !!this.unsplashState.unsplashRecords.length;\n        } else if (this.state.searchService === \"unsplash\") {\n            return !!this.unsplashState.unsplashRecords.length;\n        }\n        return super.hasContent;\n    },\n\n    get errorTitle() {\n        if (this.errorMessages[this.unsplashState.unsplashError]) {\n            return this.errorMessages[this.unsplashState.unsplashError].title;\n        }\n        return _t(\"Something went wrong\");\n    },\n\n    get errorSubtitle() {\n        if (this.errorMessages[this.unsplashState.unsplashError]) {\n            return this.errorMessages[this.unsplashState.unsplashError].subtitle;\n        }\n        return _t(\"Please check your internet connection or contact administrator.\");\n    },\n\n    get selectedRecordIds() {\n        return this.props.selectedMedia[this.props.id]\n            .filter((media) => media.mediaType === \"unsplashRecord\")\n            .map(({ id }) => id);\n    },\n\n    get isFetching() {\n        return super.isFetching || this.unsplashState.isFetchingUnsplash;\n    },\n\n    get combinedRecords() {\n        /**\n         * Creates an array with alternating elements from two arrays.\n         *\n         * @param {Array} a\n         * @param {Array} b\n         * @returns {Array} alternating elements from a and b, starting with\n         *     an element of a\n         */\n        function alternate(a, b) {\n            return [a.map((v, i) => (i < b.length ? [v, b[i]] : v)), b.slice(a.length)].flat(2);\n        }\n        return alternate(this.unsplashState.unsplashRecords, this.state.libraryMedia);\n    },\n\n    get allAttachments() {\n        return [...super.allAttachments, ...this.unsplashState.unsplashRecords];\n    },\n\n    async fetchUnsplashRecords(offset) {\n        if (!this.state.needle) {\n            return { records: [], isMaxed: false };\n        }\n        this.unsplashState.isFetchingUnsplash = true;\n        try {\n            const { isMaxed, images } = await this.unsplash.getImages(\n                this.state.needle,\n                offset,\n                this.NUMBER_OF_RECORDS_TO_DISPLAY,\n                this.props.orientation\n            );\n            this.unsplashState.isFetchingUnsplash = false;\n            this.unsplashState.unsplashError = false;\n            // Use a set to keep track of every image we've received so far,\n            // based on their ids. This will allow us to ignore duplicate\n            // images from Unsplash. We can assume there are no duplicates at\n            // this point as a precondition.\n            const existingIds = new Set(this.unsplashState.unsplashRecords.map(r => r.id));\n            const newImages = images.filter(record => {\n                if (existingIds.has(record.id)) {\n                    return false;\n                }\n                // Mark this image as seen so that we can ignore any duplicates\n                // from the same Unsplash batch.\n                existingIds.add(record.id);\n                return true;\n            });\n            const records = newImages.map((record) => {\n                const url = new URL(record.urls.regular);\n                // In small windows, row height could get quite a bit larger than the min, so we keep some leeway.\n                url.searchParams.set(\"h\", 2 * this.MIN_ROW_HEIGHT);\n                url.searchParams.delete(\"w\");\n                return Object.assign({}, record, {\n                    url: url.toString(),\n                    mediaType: \"unsplashRecord\",\n                });\n            });\n            return { isMaxed, records };\n        } catch (e) {\n            this.unsplashState.isFetchingUnsplash = false;\n            if (e === \"no_access\") {\n                this.unsplashState.useUnsplash = false;\n            } else {\n                this.unsplashState.unsplashError = e;\n            }\n            return { records: [], isMaxed: true };\n        }\n    },\n\n    async loadMore(...args) {\n        await super.loadMore(...args);\n        return this.keepLastUnsplash\n            .add(this.fetchUnsplashRecords(this.unsplashState.unsplashRecords.length))\n            .then(({ records, isMaxed }) => {\n                // This is never reached if another search or loadMore occurred.\n                this.unsplashState.unsplashRecords.push(...records);\n                this.unsplashState.isMaxed = isMaxed;\n            });\n    },\n\n    async search(...args) {\n        await super.search(...args);\n        await this.searchUnsplash();\n    },\n\n    async searchUnsplash() {\n        if (!this.state.needle) {\n            this.unsplashState.unsplashError = false;\n            this.unsplashState.unsplashRecords = [];\n            this.unsplashState.isMaxed = false;\n        }\n        return this.keepLastUnsplash\n            .add(this.fetchUnsplashRecords(0))\n            .then(({ records, isMaxed }) => {\n                // This is never reached if a new search occurred.\n                this.unsplashState.unsplashRecords = records;\n                this.unsplashState.isMaxed = isMaxed;\n            });\n    },\n\n    async onClickRecord(media) {\n        this.props.selectMedia({ ...media, mediaType: \"unsplashRecord\", query: this.state.needle });\n        if (!this.props.multiSelect) {\n            await this.props.save();\n        }\n    },\n\n    async submitCredentials(key, appId) {\n        this.unsplashState.unsplashError = null;\n        await rpc(\"/web_unsplash/save_unsplash\", { key, appId });\n        await this.searchUnsplash();\n    },\n});\n\nImageSelector.components = {\n    ...ImageSelector.components,\n    UnsplashError,\n};\n", "import { MediaDialog, TABS } from \"@html_editor/main/media/media_dialog/media_dialog\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { useService } from \"@web/core/utils/hooks\";\n\npatch(MediaDialog.prototype, {\n    setup() {\n        super.setup();\n        this.unsplashService = useService(\"unsplash\");\n    },\n\n    async save() {\n        const selectedImages = this.selectedMedia[TABS.IMAGES.id];\n        if (selectedImages) {\n            const unsplashRecords = selectedImages.filter(\n                (media) => media.mediaType === \"unsplashRecord\"\n            );\n            if (unsplashRecords.length) {\n                await this.unsplashService.uploadUnsplashRecords(\n                    unsplashRecords,\n                    { resModel: this.props.resModel, resId: this.props.resId },\n                    (attachments) => {\n                        this.selectedMedia[TABS.IMAGES.id] = this.selectedMedia[\n                            TABS.IMAGES.id\n                        ].filter((media) => media.mediaType !== \"unsplashRecord\");\n                        this.selectedMedia[TABS.IMAGES.id] = this.selectedMedia[\n                            TABS.IMAGES.id\n                        ].concat(\n                            attachments.map((attachment) => ({\n                                ...attachment,\n                                mediaType: \"attachment\",\n                            }))\n                        );\n                    }\n                );\n            }\n        }\n        return super.save(...arguments);\n    },\n});\n", "import { Component, useState } from \"@odoo/owl\";\n\nexport class UnsplashCredentials extends Component {\n    static template = \"web_unsplash.UnsplashCredentials\";\n    static props = {\n        submitCredentials: Function,\n        hasCredentialsError: Boolean,\n    };\n    setup() {\n        this.state = useState({\n            key: \"\",\n            appId: \"\",\n            hasKeyError: this.props.hasCredentialsError,\n            hasAppIdError: this.props.hasCredentialsError,\n        });\n    }\n\n    submitCredentials() {\n        if (this.state.key === \"\") {\n            this.state.hasKeyError = true;\n        } else if (this.state.appId === \"\") {\n            this.state.hasAppIdError = true;\n        } else {\n            this.props.submitCredentials(this.state.key, this.state.appId);\n        }\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { UnsplashCredentials } from \"../unsplash_credentials/unsplash_credentials\";\n\nexport class UnsplashError extends Component {\n    static template = \"web_unsplash.UnsplashError\";\n    static components = {\n        UnsplashCredentials,\n    };\n    static props = {\n        title: String,\n        subtitle: String,\n        showCredentials: Boolean,\n        submitCredentials: { type: Function, optional: true },\n        hasCredentialsError: { type: Boolean, optional: true },\n    };\n}\n", "import { rpc } from \"@web/core/network/rpc\";\nimport { registry } from \"@web/core/registry\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { AUTOCLOSE_DELAY } from \"@html_editor/main/media/media_dialog/upload_progress_toast/upload_service\";\n\nexport const unsplashService = {\n    dependencies: [\"upload\"],\n    async start(env, { upload }) {\n        const _cache = {};\n        return {\n            async uploadUnsplashRecords(records, { resModel, resId }, onUploaded) {\n                upload.incrementId();\n                const file = upload.addFile({\n                    id: upload.fileId,\n                    name:\n                        records.length > 1\n                            ? _t(\"Uploading %(count)s '%(query)s' images.\", {\n                                  count: records.length,\n                                  query: records[0].query,\n                              })\n                            : _t(\"Uploading '%s' image.\", records[0].query),\n                });\n\n                try {\n                    const urls = {};\n                    for (const record of records) {\n                        const _1920Url = new URL(record.urls.regular);\n                        _1920Url.searchParams.set(\"w\", \"1920\");\n                        urls[record.id] = {\n                            url: _1920Url.href,\n                            download_url: record.links.download_location,\n                            description: record.alt_description,\n                        };\n                    }\n\n                    const xhr = new XMLHttpRequest();\n                    xhr.upload.addEventListener(\"progress\", (ev) => {\n                        const rpcComplete = (ev.loaded / ev.total) * 100;\n                        file.progress = rpcComplete;\n                    });\n                    xhr.upload.addEventListener(\"load\", function () {\n                        // Don't show yet success as backend code only starts now\n                        file.progress = 100;\n                    });\n                    const attachments = await rpc(\n                        \"/web_unsplash/attachment/add\",\n                        {\n                            res_id: resId,\n                            res_model: resModel,\n                            unsplashurls: urls,\n                            query: records[0].query,\n                        },\n                        { xhr }\n                    );\n\n                    if (attachments.error) {\n                        file.hasError = true;\n                        file.errorMessage = attachments.error;\n                    } else {\n                        file.uploaded = true;\n                        await onUploaded(attachments);\n                    }\n                    setTimeout(() => upload.deleteFile(file.id), AUTOCLOSE_DELAY);\n                } catch (error) {\n                    file.hasError = true;\n                    setTimeout(() => upload.deleteFile(file.id), AUTOCLOSE_DELAY);\n                    throw error;\n                }\n            },\n\n            async getImages(query, offset = 0, pageSize = 30, orientation) {\n                const from = offset;\n                const to = offset + pageSize;\n                // Use orientation in the cache key to not show images in cache\n                // when using the same query word but changing the orientation\n                let cachedData = orientation ? _cache[query + orientation] : _cache[query];\n\n                if (\n                    cachedData &&\n                    (cachedData.images.length >= to ||\n                        (cachedData.totalImages !== 0 && cachedData.totalImages < to))\n                ) {\n                    return {\n                        images: cachedData.images.slice(from, to),\n                        isMaxed: to > cachedData.totalImages,\n                    };\n                }\n                cachedData = await this._fetchImages(query, orientation);\n                return {\n                    images: cachedData.images.slice(from, to),\n                    isMaxed: to > cachedData.totalImages,\n                };\n            },\n            /**\n             * Fetches images from unsplash and stores it in cache\n             */\n            async _fetchImages(query, orientation) {\n                const key = orientation ? query + orientation : query;\n                if (!_cache[key]) {\n                    _cache[key] = {\n                        images: [],\n                        maxPages: 0,\n                        totalImages: 0,\n                        pageCached: 0,\n                    };\n                }\n                const cachedData = _cache[key];\n                const payload = {\n                    query: query,\n                    page: cachedData.pageCached + 1,\n                    per_page: 30, // max size from unsplash API\n                };\n                if (orientation) {\n                    payload.orientation = orientation;\n                }\n                const result = await rpc(\"/web_unsplash/fetch_images\", payload);\n                if (result.error) {\n                    return Promise.reject(result.error);\n                }\n                cachedData.pageCached++;\n                cachedData.images.push(...result.results);\n                cachedData.maxPages = result.total_pages;\n                cachedData.totalImages = result.total;\n                return cachedData;\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"unsplash\", unsplashService);\n", "import {\n    Component,\n    markup,\n    onMounted,\n    onWillStart,\n    onWillUnmount,\n    onWillUpdateProps,\n    useEffect,\n    useRef,\n    useState,\n} from \"@odoo/owl\";\nimport { getBundle } from \"@web/core/assets\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { fillClipboardData } from \"@html_editor/utils/clipboard\";\nimport { fixInvalidHTML, instanceofMarkup } from \"@html_editor/utils/sanitize\";\nimport { HtmlUpgradeManager } from \"@html_editor/html_migrations/html_upgrade_manager\";\nimport { TableOfContentManager } from \"@html_editor/others/embedded_components/core/table_of_content/table_of_content_manager\";\nimport { getDeepestPosition } from \"@html_editor/utils/dom_info\";\n\nexport class HtmlViewer extends Component {\n    static template = \"html_editor.HtmlViewer\";\n    static props = {\n        config: { type: Object },\n        migrateHTML: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        migrateHTML: true,\n    };\n\n    setup() {\n        this._cleanups = [];\n        this.htmlUpgradeManager = new HtmlUpgradeManager();\n        this.iframeRef = useRef(\"iframe\");\n\n        this.state = useState({\n            iframeVisible: false,\n            value: this.formatValue(this.props.config.value),\n        });\n        this.components = new Set();\n\n        onWillUpdateProps((newProps) => {\n            const newValue = this.formatValue(newProps.config.value);\n            if (newValue.toString() !== this.state.value.toString()) {\n                this.state.value = this.formatValue(newProps.config.value);\n                if (this.props.config.embeddedComponents) {\n                    this.destroyComponents();\n                }\n                if (this.showIframe) {\n                    this.updateIframeContent(this.state.value);\n                }\n            }\n        });\n\n        onWillUnmount(() => {\n            this.destroyComponents();\n        });\n\n        if (this.showIframe) {\n            onMounted(() => {\n                const onLoadIframe = () => this.onLoadIframe(this.state.value);\n                this.iframeRef.el.addEventListener(\"load\", onLoadIframe, { once: true });\n                // Force the iframe to call the `load` event. Without this line, the\n                // event 'load' might never trigger.\n                this.iframeRef.el.after(this.iframeRef.el);\n            });\n        } else {\n            this.readonlyElementRef = useRef(\"readonlyContent\");\n            useEffect(\n                () => {\n                    this.processReadonlyContent(this.readonlyElementRef.el);\n                },\n                () => [this.props.config.value.toString(), this.readonlyElementRef?.el]\n            );\n        }\n\n        if (this.props.config.cssAssetId) {\n            onWillStart(async () => {\n                this.cssAsset = await getBundle(this.props.config.cssAssetId);\n            });\n        }\n\n        if (this.props.config.embeddedComponents) {\n            // TODO @phoenix: should readonly iframe with embedded components be supported?\n            this.embeddedComponents = memoize((embeddedComponents = []) => {\n                const result = {};\n                for (const embedding of embeddedComponents) {\n                    result[embedding.name] = embedding;\n                }\n                return result;\n            });\n            useEffect(\n                () => {\n                    if (this.readonlyElementRef?.el) {\n                        this.mountComponents();\n                    }\n                },\n                () => [this.props.config.value.toString(), this.readonlyElementRef?.el]\n            );\n            this.tocManager = new TableOfContentManager(this.readonlyElementRef);\n        }\n    }\n\n    addDomListener(target, eventName, fn, capture = false) {\n        const handler = (ev) => {\n            fn?.call(this, ev);\n        };\n        target.addEventListener(eventName, handler, capture);\n        this._cleanups.push(() => target.removeEventListener(eventName, handler, capture));\n    }\n\n    get showIframe() {\n        return this.props.config.hasFullHtml || this.props.config.cssAssetId;\n    }\n\n    /**\n     * Allows overrides to process the value used in the Html Viewer.\n     * Typically, if the value comes from the html_field, it is already fixed\n     * (invalid and obsolete elements were replaced). If used as a standalone,\n     * the HtmlViewer has to handle invalid nodes and html upgrades.\n     *\n     * @param { string | Markup } value\n     * @returns { string | Markup }\n     */\n    formatValue(value) {\n        let newVal = fixInvalidHTML(value);\n        if (this.props.migrateHTML) {\n            newVal = this.htmlUpgradeManager.processForUpgrade(newVal, {\n                containsComplexHTML: this.props.config.hasFullHtml,\n                env: this.env,\n            });\n        }\n        if (instanceofMarkup(value)) {\n            return markup(newVal);\n        }\n        return newVal;\n    }\n\n    processReadonlyContent(container) {\n        this.retargetLinks(container);\n        this.applyAccessibilityAttributes(container);\n        this.addDomListener(container, \"copy\", this.onCopy);\n    }\n\n    /**\n     * @param {ClipboardEvent} ev\n     */\n    onCopy(ev) {\n        ev.preventDefault();\n        const selection = ev.target.ownerDocument.defaultView.getSelection();\n        const [deepAnchorNode, deepAnchorOffset] = getDeepestPosition(\n            selection.anchorNode,\n            selection.anchorOffset\n        );\n        const [deepFocusNode, deepFocusOffset] = getDeepestPosition(\n            selection.focusNode,\n            selection.focusOffset\n        );\n\n        const range = new Range();\n        range.setStart(deepAnchorNode, deepAnchorOffset);\n        range.setEnd(deepFocusNode, deepFocusOffset);\n        const clonedContents = range.cloneContents();\n        fillClipboardData(ev, clonedContents);\n    }\n\n    /**\n     * Ensure that elements with accessibility editor attributes correctly get\n     * the standard accessibility attribute (aria-label, role).\n     */\n    applyAccessibilityAttributes(container) {\n        for (const el of container.querySelectorAll(\"[data-oe-role]\")) {\n            el.setAttribute(\"role\", el.dataset.oeRole);\n        }\n        for (const el of container.querySelectorAll(\"[data-oe-aria-label]\")) {\n            el.setAttribute(\"aria-label\", el.dataset.oeAriaLabel);\n        }\n    }\n\n    /**\n     * Ensure all links are opened in a new tab.\n     */\n    retargetLinks(container) {\n        for (const link of container.querySelectorAll(\"a\")) {\n            this.retargetLink(link);\n        }\n    }\n\n    retargetLink(link) {\n        link.setAttribute(\"target\", \"_blank\");\n        link.setAttribute(\"rel\", \"noreferrer\");\n    }\n\n    updateIframeContent(content) {\n        const contentWindow = this.iframeRef.el.contentWindow;\n        const iframeTarget = this.props.config.hasFullHtml\n            ? contentWindow.document.documentElement\n            : contentWindow.document.querySelector(\"#iframe_target\");\n        iframeTarget.innerHTML = content;\n        this.processReadonlyContent(iframeTarget);\n    }\n\n    onLoadIframe(value) {\n        const contentWindow = this.iframeRef.el.contentWindow;\n        if (!this.props.config.hasFullHtml) {\n            contentWindow.document.open(\"text/html\", \"replace\").write(\n                `<!DOCTYPE html><html>\n                        <head>\n                            <meta charset=\"utf-8\"/>\n                            <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>\n                            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>\n                        </head>\n                        <body class=\"o_in_iframe o_readonly\" style=\"overflow: hidden;\">\n                            <div id=\"iframe_target\"></div>\n                        </body>\n                    </html>`\n            );\n        }\n\n        if (this.cssAsset) {\n            for (const cssLib of this.cssAsset.cssLibs) {\n                const link = contentWindow.document.createElement(\"link\");\n                link.setAttribute(\"type\", \"text/css\");\n                link.setAttribute(\"rel\", \"stylesheet\");\n                link.setAttribute(\"href\", cssLib);\n                contentWindow.document.head.append(link);\n            }\n        }\n\n        this.updateIframeContent(this.state.value);\n        this.state.iframeVisible = true;\n    }\n\n    //--------------------------------------------------------------------------\n    // Embedded Components\n    //--------------------------------------------------------------------------\n\n    destroyComponent({ root, host }) {\n        const { getEditableDescendants } = this.getEmbedding(host);\n        const editableDescendants = getEditableDescendants?.(host) || {};\n        root.destroy();\n        this.components.delete(arguments[0]);\n        host.append(...Object.values(editableDescendants));\n    }\n\n    destroyComponents() {\n        for (const cleanup of this._cleanups) {\n            cleanup();\n        }\n        for (const info of [...this.components]) {\n            this.destroyComponent(info);\n        }\n    }\n\n    forEachEmbeddedComponentHost(elem, callback) {\n        const selector = `[data-embedded]`;\n        const targets = [...elem.querySelectorAll(selector)];\n        if (elem.matches(selector)) {\n            targets.unshift(elem);\n        }\n        for (const host of targets) {\n            const embedding = this.getEmbedding(host);\n            if (!embedding) {\n                continue;\n            }\n            callback(host, embedding);\n        }\n    }\n\n    getEmbedding(host) {\n        return this.embeddedComponents(this.props.config.embeddedComponents)[host.dataset.embedded];\n    }\n\n    setupNewComponent({ name, env, props }) {\n        if (name === \"tableOfContent\") {\n            Object.assign(props, {\n                manager: this.tocManager,\n            });\n        }\n    }\n\n    mountComponent(host, { Component, getEditableDescendants, getProps, name }) {\n        const props = getProps?.(host) || {};\n        // TODO ABD TODO @phoenix: check if there is too much info in the htmlViewer env.\n        // i.e.: env has X because of parent component,\n        // embedded component descendant sometimes uses X from env which is set conditionally:\n        // -> it will override the one one from the parent => OK.\n        // -> it will not => the embedded component still has X in env because of its ancestors => Issue.\n        const env = Object.create(this.env);\n        if (getEditableDescendants) {\n            env.getEditableDescendants = getEditableDescendants;\n        }\n        this.setupNewComponent({\n            name,\n            env,\n            props,\n        });\n        const root = this.__owl__.app.createRoot(Component, {\n            props,\n            env,\n        });\n        const promise = root.mount(host);\n        // Don't show mounting errors as they will happen often when the host\n        // is disconnected from the DOM because of a patch\n        promise.catch();\n        // Patch mount fiber to hook into the exact call stack where root is\n        // mounted (but before). This will remove host children synchronously\n        // just before adding the root rendered html.\n        const fiber = root.node.fiber;\n        const fiberComplete = fiber.complete;\n        fiber.complete = function () {\n            host.replaceChildren();\n            fiberComplete.call(this);\n        };\n        const info = {\n            root,\n            host,\n        };\n        this.components.add(info);\n    }\n\n    mountComponents() {\n        this.forEachEmbeddedComponentHost(this.readonlyElementRef.el, (host, embedding) => {\n            this.mountComponent(host, embedding);\n        });\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { MainComponentsContainer } from \"@web/core/main_components_container\";\nimport { useForwardRefToParent } from \"@web/core/utils/hooks\";\nimport { registry } from \"@web/core/registry\";\nimport { useRegistry } from \"@web/core/registry_hook\";\n\n/**\n * TODO ABD: refactor to propagate a reactive object instead of using a registry with an identifier\n */\nexport class LocalOverlayContainer extends MainComponentsContainer {\n    static template = \"html_editor.LocalOverlayContainer\";\n    static props = {\n        localOverlay: { type: Function, optional: true },\n        identifier: { type: String, optional: true },\n    };\n    static defaultProps = {\n        identifier: \"overlay_components\",\n    };\n\n    setup() {\n        const overlayComponents = registry.category(this.props.identifier);\n        // todo: remove this somehow\n        if (!overlayComponents.validationSchema) {\n            overlayComponents.addValidation({\n                Component: { validate: (c) => c.prototype instanceof Component },\n                props: { type: Object, optional: true },\n            });\n        }\n        this.Components = useRegistry(overlayComponents);\n        useForwardRefToParent(\"localOverlay\");\n    }\n}\n", "import { ancestors } from \"@html_editor/utils/dom_traversal\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { couldBeScrollableX, couldBeScrollableY } from \"@web/core/utils/scrolling\";\nimport { useComponent, useEffect } from \"@odoo/owl\";\n\n/**\n * This hook has the same job as the PositionPlugin, but for Components.\n * It was created to be used within the Html Viewer and still have overlays.\n *\n * TODO ABD: refactor html viewer to: either use a plugin system, or generalize\n * the positioning logic so that both the plugin and the hook can use it.\n */\nexport function usePositionHook(containerRef, document, callback) {\n    const comp = useComponent();\n    const onLayoutGeometryChange = throttleForAnimation(callback.bind(comp));\n    const resizeObserver = new ResizeObserver(onLayoutGeometryChange);\n    const cleanups = [];\n    const addDomListener = (target, eventName, capture) => {\n        target.addEventListener(eventName, onLayoutGeometryChange, capture);\n        cleanups.push(() => target.removeEventListener(eventName, onLayoutGeometryChange, capture));\n    };\n    useEffect(\n        () => {\n            if (containerRef.el) {\n                resizeObserver.observe(document.body);\n                resizeObserver.observe(containerRef.el);\n                addDomListener(window, \"resize\");\n                if (document.defaultView !== window) {\n                    addDomListener(document.defaultView, \"resize\");\n                }\n                addDomListener(document, \"scroll\");\n                const scrollableElements = [containerRef.el, ...ancestors(containerRef.el)].filter(\n                    (node) => couldBeScrollableX(node) || couldBeScrollableY(node)\n                );\n                for (const scrollableElement of scrollableElements) {\n                    addDomListener(scrollableElement, \"scroll\");\n                    resizeObserver.observe(scrollableElement);\n                }\n            }\n            return () => {\n                resizeObserver.disconnect();\n                for (const cleanup of cleanups.toReversed()) {\n                    cleanup();\n                    cleanups.pop();\n                }\n            };\n        },\n        () => [containerRef.el]\n    );\n}\n", "import { registry } from \"@web/core/registry\";\n\nexport function htmlEditorVersions() {\n    return Object.keys(registry.category(\"html_editor_upgrade\").subRegistries).sort(\n        compareVersions\n    );\n}\n\nexport const VERSION_SELECTOR = \"[data-oe-version]\";\n\nexport function stripVersion(element) {\n    element.querySelectorAll(VERSION_SELECTOR).forEach((el) => {\n        delete el.dataset.oeVersion;\n    });\n}\n\n/**\n * Compare 2 versions\n *\n * @param {string} version1\n * @param {string} version2\n * @returns {number} -1 if version1 < version2\n *                   0 if version1 === version2\n *                   1 if version1 > version2\n */\nexport function compareVersions(version1, version2) {\n    version1 = version1.split(\".\").map((v) => parseInt(v));\n    version2 = version2.split(\".\").map((v) => parseInt(v));\n    if (version1[0] < version2[0] || (version1[0] === version2[0] && version1[1] < version2[1])) {\n        return -1;\n    } else if (version1[0] === version2[0] && version1[1] === version2[1]) {\n        return 0;\n    } else {\n        return 1;\n    }\n}\n", "import { markup } from \"@odoo/owl\";\nimport {\n    compareVersions,\n    VERSION_SELECTOR,\n    htmlEditorVersions,\n} from \"@html_editor/html_migrations/html_migrations_utils\";\nimport { registry } from \"@web/core/registry\";\nimport { fixInvalidHTML } from \"@html_editor/utils/sanitize\";\n\n/**\n * Handle HTML transformations dependent on the current implementation of the\n * editor and its plugins for HtmlField values that were not upgraded through\n * conventional means (python upgrade script), i.e. modify obsolete\n * classes/style, convert deprecated Knowledge Behaviors to their\n * EmbeddedComponent counterparts, ...\n *\n * How to use:\n * - Create a file to export a `migrate(element, env)` function which applies\n *   the necessary modifications inside `element` related to a specific version:\n *    - HTMLElement `element`: a container for the HtmlField value\n *    - Object `env`: the typical `owl` environment (can be used to check\n *      the current record data, use a service, ...).\n * !!!  ALWAYS assume that the `env` may not have the resource used in your\n *      migrate function and adjust accordingly.\n * - Refer to that file in the `html_editor_upgrade` registry, in the version\n *   category related to your change: `major.minor` (bump major for a change in\n *   master, and minor for a change in stable), in a sub-category related to\n *   your module.\n *   Example for the version 1.1 in `html_editor`:\n *   `registry\n *        .category(\"html_editor_upgrade\")\n *        .category(\"1.1\")\n *        .add(\"html_editor\", \"@html_editor/html_migrations/migration-1.1\")`\n */\nexport class HtmlUpgradeManager {\n    constructor() {\n        this.upgradeRegistry = registry.category(\"html_editor_upgrade\");\n        this.parser = new DOMParser();\n        this.originalValue = undefined;\n        this.upgradedValue = undefined;\n        this.element = undefined;\n        this.env = {};\n    }\n\n    get value() {\n        return this.upgradedValue;\n    }\n\n    processForUpgrade(value, { containsComplexHTML, env } = {}) {\n        this.env = env || {};\n        this.containsComplexHTML = containsComplexHTML;\n        const strValue = value.toString();\n        if (\n            strValue === this.originalValue?.toString() ||\n            strValue === this.upgradedValue?.toString()\n        ) {\n            return this.value;\n        }\n        this.originalValue = value;\n        this.upgradedValue = value;\n        this.element = this.parser.parseFromString(fixInvalidHTML(value), \"text/html\")[\n            this.containsComplexHTML ? \"documentElement\" : \"body\"\n        ];\n        const versionNode = this.element.querySelector(VERSION_SELECTOR);\n        const version = versionNode?.dataset.oeVersion || \"0.0\";\n        const VERSIONS = htmlEditorVersions();\n        const currentVersion = VERSIONS.at(-1);\n        if (!currentVersion || version === currentVersion) {\n            return this.value;\n        }\n        try {\n            const upgradeSequence = VERSIONS.filter(\n                (subVersion) =>\n                    // skip already applied versions\n                    compareVersions(subVersion, version) > 0\n            );\n            this.upgradedValue = this.upgrade(upgradeSequence);\n        } catch {\n            // If an upgrade fails, silently continue to use the raw value.\n        }\n        return this.value;\n    }\n\n    upgrade(upgradeSequence) {\n        for (const version of upgradeSequence) {\n            const modules = this.upgradeRegistry.category(version);\n            for (const [key, module] of modules.getEntries()) {\n                const migrate = odoo.loader.modules.get(module).migrate;\n                if (!migrate) {\n                    console.error(\n                        `A \"${key}\" migrate function could not be found at \"${module}\" or it did not load.`\n                    );\n                }\n                migrate(this.element, this.env);\n            }\n        }\n        return markup(this.element[this.containsComplexHTML ? \"outerHTML\" : \"innerHTML\"]);\n    }\n}\n", "import { registry } from \"@web/core/registry\";\n\n// See `HtmlUpgradeManager` docstring for usage details.\nconst html_upgrade = registry.category(\"html_editor_upgrade\");\n\n// Introduction of embedded components based on Knowledge Behaviors (Odoo 18).\nhtml_upgrade.category(\"1.0\");\n\n// Remove the Excalidraw EmbeddedComponent and replace it with a link.\nhtml_upgrade.category(\"1.1\").add(\"html_editor\", \"@html_editor/html_migrations/migration-1.1\");\n\n// Fix Banner classes to properly handle `contenteditable` attribute\nhtml_upgrade.category(\"1.2\").add(\"html_editor\", \"@html_editor/html_migrations/migration-1.2\");\n\n// Knowledge embeddedViews favorite irFilters should have a `user_ids` property.\nhtml_upgrade.category(\"2.0\");\n", "/**\n * Remove the Excalidraw EmbeddedComponent and replace it with a link\n *\n * @param {HTMLElement} container\n * @param {Object} env\n */\nexport function migrate(container) {\n    const excalidrawContainers = container.querySelectorAll(\"[data-embedded='draw']\");\n    for (const excalidrawContainer of excalidrawContainers) {\n        const source = JSON.parse(excalidrawContainer.dataset.embeddedProps).source;\n        const newParagraph = document.createElement(\"P\");\n        const anchor = document.createElement(\"A\");\n        newParagraph.append(anchor);\n        anchor.append(document.createTextNode(source));\n        anchor.href = source;\n        excalidrawContainer.after(newParagraph);\n        excalidrawContainer.remove();\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\n\nconst ARIA_LABELS = {\n    \".o_editor_banner.alert-danger\": _t(\"Banner Danger\"),\n    \".o_editor_banner.alert-info\": _t(\"Banner Info\"),\n    \".o_editor_banner.alert-success\": _t(\"Banner Success\"),\n    \".o_editor_banner.alert-warning\": _t(\"Banner Warning\"),\n};\n\nfunction getAriaLabel(element) {\n    for (const [selector, ariaLabel] of Object.entries(ARIA_LABELS)) {\n        if (element.matches(selector)) {\n            return ariaLabel;\n        }\n    }\n}\n\n/**\n * Replace the `o_editable` and `o_not_editable` on `banner` elements by\n * `o-contenteditable-true` and `o-content-editable-false`.\n * Add `o_editor_banner_content` to the content parent element.\n * Add accessibility editor-specific attributes (data-oe-role and\n * data-oe-aria-label).\n *\n * @param {HTMLElement} container\n */\nexport function migrate(container) {\n    const bannerContainers = container.querySelectorAll(\".o_editor_banner\");\n    for (const bannerContainer of bannerContainers) {\n        bannerContainer.classList.remove(\"o_not_editable\");\n        bannerContainer.classList.add(\"o-contenteditable-false\");\n        bannerContainer.dataset.oeRole = \"status\";\n        const icon = bannerContainer.querySelector(\".o_editor_banner_icon\");\n        if (icon) {\n            const ariaLabel = getAriaLabel(bannerContainer);\n            if (ariaLabel) {\n                icon.dataset.oeAriaLabel = ariaLabel;\n            }\n        }\n        const bannerContent = bannerContainer.querySelector(\".o_editor_banner_icon ~ div\");\n        if (bannerContent) {\n            bannerContent.classList.remove(\"o_editable\");\n            bannerContent.classList.add(\"o_editor_banner_content\");\n            bannerContent.classList.add(\"o-contenteditable-true\");\n        }\n    }\n}\n", "import {\n    onMounted,\n    onRendered,\n    onPatched,\n    onWillDestroy,\n    reactive,\n    toRaw,\n    useComponent,\n    useRef,\n    useState,\n} from \"@odoo/owl\";\n\n/**\n * @typedef {HTMLElement} HostElement host element for an embedded component\n * @typedef {Object} State state obtained from `useState` usage\n * @typedef {Record<string, HTMLElement>} EditableDescendants\n * @typedef {(state, previous, next) => void} PropertyUpdate function applying\n *          a state change which can be computed from `previous` and `next`\n *          to `state`.\n * @typedef {Record<string, PropertyUpdate>} PropertyUpdater\n *\n * @typedef {Object} StateChangeManagerConfig\n * @property {PropertyUpdater} [propertyUpdater] object mapping a key of the\n *        state to a function which will compute how values from a stateChange\n *        are applied to the current state. Defined in the embedding definition\n *        of a component.\n * @property {function(HostElement):State} [getEmbeddedState]\n *        custom function to get the first embedded state (the one used during\n *        setup), in case not all embedded props should be part of the state, or\n *        if more properties should be added to it.\n * @property {function(HostElement, State):Object} [stateToEmbeddedProps]\n *        custom function to compute the props, i.e. in case the entire state\n *        should not be converted to props.\n *\n * @typedef {Object} Embedding object provided to the instance which mounts\n *          Embedded components (EmbeddedComponentPlugin, HtmlViewer, ...)\n * @property {String} name\n * @property {Component} Component\n * @property {function(HostElement):Object} getProps props for the given\n *           Component class instance.\n * @property {function(HostElement):EditableDescendants} [getEditableDescendants]\n *           @see useEditableDescendants\n * @property {function(StateChangeManagerConfig):StateChangeManager} [getStateChangeManager]\n *           @see useEmbeddedState\n */\n\n/**\n * Get all element children with `data-embedded-editable` attribute which are\n * descendants of the host's own embedded component and not part of another\n * embedded component descendant (an embedded component can contain others).\n * If multiple elements have the same `data-embedded-editable`, only the last\n * one is considered.\n * @param {HostElement} host\n * @returns {EditableDescendants} editableDescendants\n */\nexport function getEditableDescendants(host) {\n    const editableDescendants = {};\n    for (const candidate of host.querySelectorAll(\"[data-embedded-editable]\")) {\n        if (candidate.closest(\"[data-embedded]\") === host) {\n            editableDescendants[candidate.dataset.embeddedEditable] = candidate;\n        }\n    }\n    return editableDescendants;\n}\n\n/**\n * Handle the rendering of editableDescendants:\n * It is a node owned by the editor, which will be inserted under a ref of\n * the same name as the attribute `data-embedded-editable` of that node, in the\n * component's template. This allows to use editor features inside an embedded\n * component. EditableDescendants are shared in collaboration and are saved\n * between edition sessions.\n *\n * Warning: there must be a ref in the template for every editableDescendants,\n * available at all times no matter the component state to guarantee that the\n * editor can save their values at any given time, synchronously.\n *\n * @param {HostElement} host\n * @returns {EditableDescendants} (HTMLElement) by the value of their\n *          `data-embedded-editable` attribute.\n */\nexport function useEditableDescendants(host) {\n    const component = useComponent();\n    if (!component.env.getEditableDescendants) {\n        throw new Error(\n            \"Missing `getEditableDescendants` function in the `embedding` provided to the `EmbeddedComponentPlugin`.\"\n        );\n    }\n    const editableDescendants = Object.freeze(component.env.getEditableDescendants(host));\n    const refs = {};\n    const renders = {};\n    for (const name of Object.keys(editableDescendants)) {\n        refs[name] = useRef(name);\n        renders[name] = () => refs[name].el.replaceChildren(editableDescendants[name]);\n    }\n    let _restoreSelection;\n    const restoreSelection = () => {\n        if (_restoreSelection) {\n            _restoreSelection();\n            _restoreSelection = undefined;\n        }\n    };\n    if (component.env.editorShared?.selection) {\n        onRendered(() => {\n            _restoreSelection = component.env.editorShared.selection.preserveSelection().restore;\n        });\n    }\n    onMounted(() => {\n        for (const render of Object.values(renders)) {\n            render();\n        }\n        restoreSelection();\n    });\n    onPatched(() => {\n        for (const [name, render] of Object.entries(renders)) {\n            // Handle partial patch\n            if (!host.contains(editableDescendants[name])) {\n                render();\n            }\n        }\n        restoreSelection();\n    });\n    return editableDescendants;\n}\n\n/**\n * Create a ProxyHandler to manage a serializable \"buffer\" (Proxy target) for\n * changes. The buffer must be a @see reactive which should update state\n * with its callback (commit).\n * @see useEmbeddedState\n * The Proxy target and state must be serializable through JSON.stringify.\n *\n * @param {Object} state\n * @param {Object} stateChangeManager\n * @param {Object} stateChangeManager.previousEmbeddedState null, or a deep copy\n *        of the target used as a reference point for comparison\n *        (before <-> after) so that multiple synchronous changes can be handled\n *        at once.\n * @returns {ProxyHandler}\n */\nfunction embeddedStateProxyHandler(state, stateChangeManager) {\n    return {\n        // Write operations are always done on the target (\"buffer\").\n        // During the first write operation before a commit, keep a deep copy of\n        // the target through serialization, which will be used as a reference\n        // point for a comparison (before <-> after).\n        set(target, key, value, receiver) {\n            if (\n                value !== Reflect.get(target, key, receiver) &&\n                !stateChangeManager.previousEmbeddedState\n            ) {\n                stateChangeManager.previousEmbeddedState = JSON.parse(\n                    JSON.stringify(stateChangeManager.embeddedState)\n                );\n            }\n            return Reflect.set(target, key, value, receiver);\n        },\n        deleteProperty(target, key) {\n            if (Reflect.has(target, key) && !stateChangeManager.previousEmbeddedState) {\n                stateChangeManager.previousEmbeddedState = JSON.parse(\n                    JSON.stringify(stateChangeManager.embeddedState)\n                );\n            }\n            return Reflect.deleteProperty(target, key);\n        },\n        // Read operations should also be done on state to register the\n        // rendering callback.\n        get(target, key, receiver) {\n            Reflect.get(state, key, state);\n            return Reflect.get(target, key, receiver);\n        },\n        ownKeys(target) {\n            Reflect.ownKeys(state);\n            return Reflect.ownKeys(target);\n        },\n        has(target, key) {\n            Reflect.has(state, key);\n            return Reflect.has(target, key);\n        },\n    };\n}\n\nfunction observeAllKeys(reactive) {\n    for (const key in reactive) {\n        const prop = reactive[key];\n        if (prop instanceof Object) {\n            observeAllKeys(prop);\n        }\n    }\n}\n\n/**\n * Extract props serialized in `data-embedded-props` attribute.\n *\n * @param {HostElement} host\n * @returns {Object} props\n */\nexport function getEmbeddedProps(host) {\n    return host.dataset.embeddedProps ? JSON.parse(host.dataset.embeddedProps) : {};\n}\n\nfunction sortedCopy(obj) {\n    const result = {};\n    const propNames = Object.keys(obj).sort();\n    for (const propName of propNames) {\n        result[propName] = obj[propName];\n    }\n    return result;\n}\n\n/**\n * Compute the difference between next and previous, and apply that difference\n * to container[key]. Comparison is done through JSON.stringify, so all values\n * must be serializable.\n *\n * @param {Object} container\n * @param {string} key\n * @param {Object} previous\n * @param {Object} next\n */\nexport function applyObjectPropertyDifference(container, key, previous, next) {\n    if (!container[key]) {\n        container[key] = {};\n    }\n    const obj1 = { ...(previous || {}) };\n    const obj2 = { ...(next || {}) };\n    const dest = container[key];\n    for (const key in obj2) {\n        if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {\n            dest[key] = obj2[key];\n        }\n        delete obj1[key];\n    }\n    for (const key in obj1) {\n        delete dest[key];\n    }\n    if (!Object.keys(dest).length && !next) {\n        delete container[key];\n    }\n}\n\n/**\n * Overwrite container[key] with value.\n *\n * @param {Object} container\n * @param {string} key\n * @param {Object} value\n */\nexport function replaceProperty(container, key, value) {\n    if (value === undefined) {\n        delete container[key];\n    } else {\n        container[key] = value;\n    }\n}\n\nexport class StateChangeManager {\n    /**\n     * @param {StateChangeManagerConfig} config\n     * @param {HostElement} config.host\n     * @param {Function} config.commitChanges notify the host that we can commit\n     *                                        changes\n     */\n    constructor(config) {\n        this.config = config;\n    }\n    setup() {\n        const defaultState = sortedCopy(this.getEmbeddedState());\n        const defaultStateChange = {\n            stateChangeId: null,\n            previous: defaultState,\n            next: defaultState,\n        };\n        // Used in case `data-embedded-state` is removed (i.e. when reverting\n        // the first mutation setting that attribute)\n        this.defaultStateChange = defaultStateChange;\n        // Used to keep track of the last applied stateChange, to avoid\n        // applying it multiple times (i.e. revertMutations + stageRecords\n        // during undo)\n        this.previousStateChange = defaultStateChange;\n        // Used to discard batch changes when a component is destroyed,\n        // pending state changes should not be applied\n        this.batchId = 0;\n        this.setupUnmounted();\n    }\n\n    /**\n     * Called at setup and when an embedded component is destroyed. This resets\n     * state values related to the mounted component. State changes will be\n     * handled differently when unmounted.\n     */\n    setupUnmounted() {\n        this.previousEmbeddedState = null;\n        this.state = null;\n        this.embeddedState = null;\n        this.embeddedStateProxy = null;\n        this.isLiveComponent = false;\n        this.batchId += 1;\n    }\n\n    /**\n     * Construct the proxy object to use inside an embedded component. It can\n     * be read on to register for rendering updates in the component template,\n     * and written on to trigger a re-rendering, sharing changes in\n     * collaboration and registering them for the history.\n     * @param {Object} state\n     * @returns {Proxy} embeddedStateProxy\n     */\n    constructEmbeddedState(state) {\n        this.state = state;\n        this.embeddedState = reactive(\n            this.assignDeepProxyCopy({}, state),\n            this.batchedChangeState()\n        );\n        this.embeddedStateProxy = new Proxy(\n            this.embeddedState,\n            embeddedStateProxyHandler(state, this)\n        );\n        // First subscription to changes.\n        observeAllKeys(this.embeddedStateProxy);\n        this.isLiveComponent = true;\n        return this.embeddedStateProxy;\n    }\n\n    /**\n     * Depending on whether the component is destroyed or started mounting,\n     * return its effective state.\n     * @returns {Object} state\n     */\n    getState() {\n        let state = this.state;\n        if (!this.isLiveComponent) {\n            state = this.getEmbeddedState();\n        }\n        return state;\n    }\n\n    /**\n     * Called when `data-embedded-state` attribute is being changed. This\n     * will update the state, the embedded state, the embedded props and\n     * recompute a new expression when necessary.\n     * @param {string} attrState JSON representation of a stateChange\n     * @param { Object } options\n     * @param {boolean} options.reverse whether to read the stateChange from\n     *        next to previous\n     * @param {boolean} options.forNewStep whether the attribute change is being\n     *        used to create a new step.\n     * @returns {string} new JSON representation of a stateChange, in case\n     *          it needs to be represented under another form to be shared\n     *          in collaboration (a local peer doing revertMutations implies\n     *          that collaborators will do applyMutations, so the stateChange\n     *          must be expressed with another form for them).\n     */\n    onStateChanged(attrState, { reverse = false, forNewStep = false } = {}) {\n        const stateChange = attrState ? JSON.parse(attrState) : this.defaultStateChange;\n        const state = this.getState();\n        if (reverse) {\n            this.reverseStateChange(stateChange);\n        }\n        if (!this.areStateChangesEqual(this.previousStateChange, stateChange)) {\n            const previous = JSON.stringify(sortedCopy(state));\n            this.commitStateChange(state, stateChange.previous, stateChange.next);\n            const sortedState = sortedCopy(state);\n            this.config.host.dataset.embeddedProps = JSON.stringify(\n                this.stateToEmbeddedProps(this.config.host, sortedState)\n            );\n            if (this.isLiveComponent && !this.previousEmbeddedState) {\n                // Update the embeddedState only if there is no pending change.\n                // If there is a pending change, it will be updated when the\n                // pending change is applied in `changeState`.\n                this.assignDeepProxyCopy(toRaw(this.embeddedState), sortedState);\n            }\n            if (!forNewStep) {\n                this.previousStateChange = stateChange;\n            } else {\n                // If mutations are being applied to create a new step, the\n                // state change must be expressed under another form for\n                // collaborators, since the collaborator will always\n                // \"applyMutations\" and never \"revertMutations\" when receiving\n                // external steps.\n                const next = JSON.stringify(sortedState);\n                if (previous !== next) {\n                    this.previousStateChange = {\n                        stateChangeId: this.generateId(),\n                        previous: JSON.parse(previous),\n                        next: JSON.parse(next),\n                    };\n                    return JSON.stringify(this.previousStateChange);\n                }\n            }\n        }\n    }\n\n    /**\n     * Allow to write on the embeddedState multiple times synchronously\n     * and batch all changes at once afterwards. A batch is discarded as soon\n     * as the component is destroyed.\n     * @returns {Function} batched changeState\n     */\n    batchedChangeState() {\n        let scheduled = false;\n        const batchId = this.batchId;\n        return async () => {\n            if (this.isLiveComponent && !scheduled) {\n                scheduled = true;\n                await Promise.resolve();\n                scheduled = false;\n                if (batchId === this.batchId) {\n                    this.changeState();\n                }\n            }\n        };\n    }\n\n    /**\n     * Apply a stateChange that was done on the embeddedState to the state,\n     * to trigger a re-rendering, and write the stateChange in\n     * `data-embedded-state` for the history and collaboration. Also\n     * recompute `data-embedded-props` for the next mounting operation.\n     */\n    changeState() {\n        if (!this.previousEmbeddedState) {\n            // If there is no previousEmbeddedState, it means that no\n            // effective change was performed, so there is nothing to commit.\n            return;\n        }\n        const previousEmbeddedState = this.previousEmbeddedState;\n        this.previousEmbeddedState = null;\n        const previous = JSON.stringify(sortedCopy(this.state));\n        this.commitStateChange(\n            this.state,\n            previousEmbeddedState,\n            JSON.parse(JSON.stringify(this.embeddedState))\n        );\n        const sortedState = sortedCopy(this.state);\n        const next = JSON.stringify(sortedState);\n        this.assignDeepProxyCopy(toRaw(this.embeddedState), sortedState);\n        if (previous !== next) {\n            this.previousStateChange = {\n                stateChangeId: this.generateId(),\n                previous: JSON.parse(previous),\n                next: JSON.parse(next),\n            };\n            this.config.host.dataset.embeddedState = JSON.stringify(this.previousStateChange);\n            this.config.host.dataset.embeddedProps = JSON.stringify(\n                this.stateToEmbeddedProps(this.config.host, sortedState)\n            );\n            this.config.commitStateChanges();\n        }\n        observeAllKeys(this.embeddedStateProxy);\n    }\n\n    areStateChangesEqual(sc1, sc2) {\n        return (\n            sc1.stateChangeId === sc2.stateChangeId &&\n            JSON.stringify(sc1.previous) === JSON.stringify(sc2.previous) &&\n            JSON.stringify(sc1.next) === JSON.stringify(sc2.next)\n        );\n    }\n\n    reverseStateChange(stateChange) {\n        const previous = stateChange.previous;\n        stateChange.previous = stateChange.next;\n        stateChange.next = previous;\n    }\n\n    /**\n     * Replace every key of target with deep proxy copies of source.\n     * This will make it so that any change at any level will pass by the\n     * embeddedStateProxyHandler traps.\n     * @param {Object} target\n     * @param {Object} source\n     * @returns {Object} copy with proxies as keys\n     */\n    assignDeepProxyCopy(target, source) {\n        for (const key of Object.keys(target)) {\n            delete target[key];\n        }\n        for (const key of Object.keys(source)) {\n            target[key] = this.deepProxyCopy(source[key]);\n        }\n        return target;\n    }\n\n    /**\n     * Create a deep proxy copy of value ensuring that any change at any level\n     * will pass by the embeddedStateProxyHandler traps.\n     * @param {Object} value\n     * @returns {Proxy} deep proxy copy of value\n     */\n    deepProxyCopy(value) {\n        if (value instanceof Object) {\n            const copy = value instanceof Array ? [] : {};\n            for (const prop in value) {\n                copy[prop] = this.deepProxyCopy(value[prop]);\n            }\n            return new Proxy(copy, embeddedStateProxyHandler(value, this));\n        }\n        return value;\n    }\n\n    generateId() {\n        return Math.floor(Math.random() * Math.pow(2, 52));\n    }\n\n    /**\n     * Apply a transaction to the active state. `previous` is the state\n     * before the transaction, and `next` is the state after the\n     * transaction was done. Keep in mind that the current state may have\n     * been changed after the transaction was done, but before it was\n     * applied. By default, will always accept nextState as\n     * the final state. `propertyUpdater` should be provided in the config\n     * to handle some keys differently, i.e. object composition.\n     * @see applyObjectPropertyDifference\n     * @param {Object} state current state\n     * @param {Object} previous state before the transaction\n     * @param {Object} next state after the transaction\n     */\n    commitStateChange(state, previous, next) {\n        const currentKeys = new Set([\n            ...Object.keys(state),\n            ...Object.keys(previous),\n            ...Object.keys(next),\n        ]);\n        for (const key of currentKeys) {\n            if (key in (this.config.propertyUpdater || {})) {\n                this.config.propertyUpdater[key](state, previous, next);\n            } else if (JSON.stringify(previous[key]) !== JSON.stringify(next[key])) {\n                replaceProperty(state, key, next[key]);\n            }\n        }\n    }\n\n    /**\n     * Extract values to be used as the first embedded state (used for setup)\n     * from the host.\n     * Extract all values from `data-embedded-props` by default.\n     * @returns {Object} state\n     */\n    getEmbeddedState() {\n        const host = this.config.host;\n        return this.config.getEmbeddedState?.(host) || getEmbeddedProps(host);\n    }\n\n    /**\n     * Convert a state to an object containing the props to be\n     * saved in `data-embedded-props`, which will be used for the next mount\n     * operation, and saved in the database. The returned object should be\n     * serializable using JSON.\n     * Return the entire state by default.\n     * @param {HostElement} host\n     * @param {Object} state\n     * @returns {Object} props\n     */\n    stateToEmbeddedProps(host, state) {\n        const props = this.config.stateToEmbeddedProps?.(host, state) || state;\n        // Clean undefined values to save space\n        for (const key of Object.keys(props)) {\n            if (props[key] === undefined) {\n                delete props[key];\n            }\n        }\n        return props;\n    }\n}\n\n/**\n * Manage updates to `data-embedded-props` (To change props given to an\n * embedded component when it will be mounted in the future), through history\n * and collaborative operations.\n * This is done through a special `embeddedState` which can be used externally\n * as a normal state.\n * That state can be modified through 2 channels:\n * - By the component itself, as with any normal state.\n * - By the embedded_component_plugin, during history or collaborative\n *   operations (undo/redo/resetStepsUntil/addExternalStep). The attribute\n *   `data-embedded-state` will be used to contain a serialized representation\n *   of a state change.\n *\n * While the embedded state evolves, the `data-embedded-props` attribute is\n * always maintained to its relative value.\n *\n * `data-embedded-state` and `data-embedded-props` attributes are maintained\n * even if the related component is in a destroyed state, in order to prepare\n * the next mount operation if the host is re-inserted in the DOM through an\n * history operation.\n * If the component is currently mounted/being mounted, state changes are\n * applied to the attribute and the embeddedState object.\n *\n * By default, a property change in the state is handled by replacing the\n * previous value with the new one (overwrite). To change this behavior,\n * provide a config extension in `getStateChangeManager` in the embedding\n * definition, with a @see propertyUpdater mapping each state key to a change\n * handler function.\n *\n * @param {HostElement} host\n * @returns {Proxy} embeddedState state which can be used for rendering, and\n *                  which is tied to the saved embedded props. Can only contain\n *                  JSON serializable values.\n */\nexport function useEmbeddedState(host) {\n    const component = useComponent();\n    if (!component.env.getStateChangeManager) {\n        throw new Error(\n            \"Missing `getStateChangeManager` function in the `embedding` provided to the `EmbeddedComponentPlugin`.\"\n        );\n    }\n    const stateChangeManager = component.env.getStateChangeManager(host);\n    onWillDestroy(() => stateChangeManager.setupUnmounted());\n    const state = useState(stateChangeManager.getEmbeddedState());\n    return stateChangeManager.constructEmbeddedState(state);\n}\n", "import { Component } from \"@odoo/owl\";\nimport { useForwardRefToParent } from \"@web/core/utils/hooks\";\n\nexport class EmbeddedComponentToolbar extends Component {\n    static props = {\n        buttonsGroupClass: { type: String, optional: true },\n        slots: Object,\n    };\n    static template = \"html_editor.EmbeddedComponentToolbar\";\n}\n\nexport class EmbeddedComponentToolbarButton extends Component {\n    static props = {\n        buttonRef: { type: Function, optional: true },\n        hidden: { type: Boolean, optional: true },\n        icon: { type: String, optional: true },\n        label: String,\n        name: { type: String, optional: true },\n        onClick: Function,\n        title: { type: String, optional: true },\n    };\n    static template = \"html_editor.EmbeddedComponentToolbarButton\";\n\n    setup() {\n        useForwardRefToParent(\"buttonRef\");\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { downloadFile } from \"@web/core/network/download\";\nimport { useFileViewer } from \"@web/core/file_viewer/file_viewer_hook\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { AlertDialog } from \"@web/core/confirmation_dialog/confirmation_dialog\";\nimport {\n    EmbeddedComponentToolbar,\n    EmbeddedComponentToolbarButton,\n} from \"@html_editor/others/embedded_components/core/embedded_component_toolbar/embedded_component_toolbar\";\nimport { StateFileModel } from \"@html_editor/others/embedded_components/core/file/state_file_model\";\nimport { getEmbeddedProps } from \"@html_editor/others/embedded_component_utils\";\nimport { Component, useState } from \"@odoo/owl\";\n\nexport class ReadonlyEmbeddedFileComponent extends Component {\n    static components = {\n        EmbeddedComponentToolbar,\n        EmbeddedComponentToolbarButton,\n    };\n    static props = {\n        fileData: { type: Object },\n        host: { type: Object },\n    };\n    static template = \"html_editor.ReadonlyEmbeddedFile\";\n\n    setup() {\n        this.dialogService = useService(\"dialog\");\n        this.state = useState({\n            fileData: { ...this.props.fileData },\n        });\n        this.fileModel = new StateFileModel(this.state);\n        this.attachmentViewer = useFileViewer();\n    }\n\n    /**\n     * This function will simply open a link that will trigger the download of\n     * the associated file. If the url is not valid, the function will display\n     * an error message.\n     */\n    async download() {\n        try {\n            await downloadFile(this.fileModel.downloadUrl);\n        } catch {\n            this.dialogService.add(AlertDialog, {\n                body: _t(\n                    \"Oops, the file %s could not be found. Please replace this file box by a new one to re-upload the file.\",\n                    this.fileModel.name\n                ),\n                title: _t(\"Missing File\"),\n                confirm: () => {},\n                confirmLabel: _t(\"Close\"),\n            });\n        }\n    }\n\n    onClickFileImage() {\n        if (this.fileModel.isViewable) {\n            this.attachmentViewer.open(this.fileModel);\n        } else {\n            this.download();\n        }\n    }\n}\n\nexport const readonlyFileEmbedding = {\n    name: \"file\",\n    Component: ReadonlyEmbeddedFileComponent,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n};\n", "import { FileModel } from \"@web/core/file_viewer/file_model\";\n\nexport class StateFileModel extends FileModel {\n    constructor(state) {\n        super();\n        this.state = state;\n        for (const property of [\n            \"access_token\",\n            \"checksum\",\n            \"extension\",\n            \"filename\",\n            \"id\",\n            \"mimetype\",\n            \"name\",\n            \"type\",\n            \"tmpUrl\",\n            \"url\",\n            \"uploading\",\n        ]) {\n            Object.defineProperty(this, property, {\n                get() {\n                    return this.state.fileData[property];\n                },\n                set(value) {\n                    this.state.fileData[property] = value;\n                },\n                configurable: true,\n                enumerable: true,\n            });\n        }\n    }\n\n    /**\n     * For embedded files stored without an `id` (i.e. demo data or old\n     * knowledge embedded files converted from \"Knowledge Behavior\"), allow\n     * direct usage of the file `url` as an `urlRoute` for the fileViewer and\n     * download attempts.\n     *\n     * @override\n     */\n    get urlRoute() {\n        if (this.isUrl && !this.id) {\n            return this.url;\n        }\n        return super.urlRoute;\n    }\n}\n", "import { Component, onMounted, onWillStart, xml } from \"@odoo/owl\";\nimport { loadBundle } from \"@web/core/assets\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { DEFAULT_LANGUAGE_ID, getPreValue, highlightPre } from \"./syntax_highlighting_utils\";\n\nexport class ReadonlySyntaxHighlightingComponent extends Component {\n    static props = {\n        value: { type: String },\n        languageId: { type: String },\n        host: { type: Object },\n    };\n    // The host is the `pre`. There's no need for a template but Owl requires it.\n    static template = xml`<span/>`;\n\n    setup() {\n        onWillStart(() =>\n            loadBundle(\n                `html_editor.assets_prism${cookie.get(\"color_scheme\") === \"dark\" ? \"_dark\" : \"\"}`,\n                { targetDoc: this.props.host.ownerDocument }\n            )\n        );\n        onMounted(() => {\n            const owlRoot = [...(this.props.host.children || [])].find(\n                (child) => child.nodeName === \"OWL-ROOT\"\n            );\n            highlightPre(owlRoot || this.props.host, this.props.value, this.props.languageId);\n        });\n    }\n}\n\nexport const readonlySyntaxHighlightingEmbedding = {\n    name: \"readonlySyntaxHighlighting\",\n    Component: ReadonlySyntaxHighlightingComponent,\n    getProps: (host) => ({\n        host,\n        languageId: host.dataset.languageId || DEFAULT_LANGUAGE_ID,\n        value: getPreValue(host),\n    }),\n};\n", "/* global Prism */\nimport { fillEmpty } from \"@html_editor/utils/dom\";\nimport { descendants, lastLeaf } from \"@html_editor/utils/dom_traversal\";\n\nexport const DEFAULT_LANGUAGE_ID = \"plaintext\";\n\n/**\n * Replace newlines in the given `element` with the appropriate number of line\n * breaks to preserve the visual aspect of these line breaks.\n *\n * @param {Element} element\n * @param {Document} [doc = element.ownerDocument || document]\n */\nexport const newlinesToLineBreaks = (element, doc = element.ownerDocument || document) => {\n    // 1. Replace \\n with <br>.\n    for (const node of descendants(element).filter((node) => node.nodeType === Node.TEXT_NODE)) {\n        let newline = node.textContent.indexOf(\"\\n\");\n        while (newline !== -1) {\n            node.before(doc.createTextNode(node.textContent.slice(0, newline)));\n            node.before(doc.createElement(\"BR\"));\n            node.textContent = node.textContent.slice(newline + 1);\n            newline = node.textContent.indexOf(\"\\n\");\n        }\n        if (!node.textContent) {\n            node.remove(); // Prevent empty trailing text node that would become the last leaf.\n        }\n    }\n    // 2. Handle trailing BRs. Eg, <span>ab\\n</span> -> <span>ab</span><br><br>\n    const trailingBr = lastLeaf(element);\n    if (trailingBr?.nodeName === \"BR\") {\n        element.append(trailingBr); // <span>ab<br></span> -> <span>ab</span><br>\n        trailingBr.after(doc.createElement(\"BR\")); // <br></pre> -> <br><br></pre>\n    }\n    // 3. Fill empty.\n    fillEmpty(element);\n};\n\n/**\n * Return the given `<pre>` element's inner text, cleaned of any zero-width\n * characters or trailing invisible newline characters (a trailing `<br>` in\n * the element's HTML is invisible but results in an visible `\\n` in its\n * `innerText` property, which would be visible if kept).\n *\n * @param {HTMLPreElement} pre\n * @returns {string}\n */\nexport const getPreValue = (pre) => {\n    // Trailing br gives \\n in innerText but should not be visible.\n    const trailingBrs = pre.innerHTML.match(/(<br>)+$/)?.length || 0;\n    return pre.innerText\n        .slice(0, pre.innerText.length - (trailingBrs > 1 ? trailingBrs - 1 : trailingBrs))\n        .replace(/[\\u200B\\uFEFF]/g, \"\");\n};\n\n/**\n * Use the Prism library to highlight the given HTML `value` with the given\n * `languageId` and replace the given `pre`'s inner HTML with it.\n *\n * @param {HTMLPreElement} pre\n * @param {string} value\n * @param {string} languageId\n */\nexport const highlightPre = (pre, value, languageId) => {\n    // We need a temporary element because directly changing the HTML of the\n    // PRE, or using replaceChildren both mess up the history by not\n    // recording the removal of the contents.\n    const fakeElement = pre.ownerDocument.createElement(\"pre\");\n    if (window.Prism) {\n        fakeElement.innerHTML = Prism.highlight(value, Prism.languages[languageId], languageId);\n    } else {\n        fakeElement.innerHTML = value;\n    }\n\n    // Post-process highlighted HTML.\n    newlinesToLineBreaks(fakeElement, pre.ownerDocument);\n\n    // Replace the PRE's contents with the highlighted ones.\n    [...pre.childNodes].forEach((child) => child.remove());\n    [...fakeElement.childNodes].forEach((child) => pre.append(child));\n};\n", "import { Component, onWillStart, useState } from \"@odoo/owl\";\nimport { TableOfContentManager } from \"@html_editor/others/embedded_components/core/table_of_content/table_of_content_manager\";\n\nexport class EmbeddedTableOfContentComponent extends Component {\n    static template = \"html_editor.EmbeddedTableOfContent\";\n    static props = {\n        manager: { type: TableOfContentManager },\n        readonly: { type: Boolean, optional: true },\n    };\n\n    setup() {\n        this.state = useState({ toc: this.props.manager.structure, folded: false });\n        onWillStart(async () => {\n            await this.props.manager.batchedUpdateStructure();\n        });\n    }\n\n    displayTocHint() {\n        return this.state.toc.headings.length < 2 && !this.props.readonly;\n    }\n\n    /**\n     * @param {Object} heading\n     */\n    onTocLinkClick(heading) {\n        this.props.manager.scrollIntoView(heading);\n    }\n}\n\nexport const tableOfContentEmbedding = {\n    name: \"tableOfContent\",\n    Component: EmbeddedTableOfContentComponent,\n};\n\nexport const readonlyTableOfContentEmbedding = {\n    name: \"tableOfContent\",\n    Component: EmbeddedTableOfContentComponent,\n    getProps: (host) => ({\n        readonly: true,\n    }),\n};\n", "import { batched, reactive } from \"@odoo/owl\";\n\nexport const HEADINGS = [\"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\"];\n\nexport class TableOfContentManager {\n    constructor(containerRef) {\n        this.containerRef = containerRef;\n        this.structure = reactive({\n            headings: [],\n        });\n        this.batchedUpdateStructure = batched(this.updateStructure.bind(this));\n    }\n\n    getContainerEl() {\n        return this.containerRef.el;\n    }\n\n    /**\n     * Allows to fetch relevant headings in the page when building the Table of Content.\n     * Will filter out things we don't want:\n     * - Empty headers\n     * - Headers only containing the 'ZeroWidthSpace' element ('\\u200B')\n     * - Headers descendants of an element with `data-embedded`\n     *\n     * @param {Element} element\n     */\n    fetchValidHeadings(element) {\n        const inEmbeddedHeadings = new Set(\n            element.querySelectorAll(\n                HEADINGS.map((heading) => `[data-embedded] ${heading}`).join(\",\")\n            )\n        );\n        return Array.from(element.querySelectorAll(HEADINGS.join(\",\")))\n            .filter((heading) => heading.innerText.trim().replaceAll(\"\\u200B\", \"\").length > 0)\n            .filter((heading) => !inEmbeddedHeadings.has(heading));\n    }\n\n    scrollIntoView(heading) {\n        if (!heading) {\n            return;\n        }\n        const { target } = heading;\n        target.scrollIntoView({ behavior: \"smooth\" });\n        target.classList.add(\"o_embedded_toc_header_highlight\");\n        window.setTimeout(() => {\n            target.classList.remove(\"o_embedded_toc_header_highlight\");\n        }, 2000);\n    }\n\n    updateStructure() {\n        const container = this.getContainerEl();\n        if (!container) {\n            return;\n        }\n        const tagDepthStack = [];\n        this.structure.headings = this.fetchValidHeadings(container).map((heading) => {\n            while (tagDepthStack.at(-1) >= heading.tagName) {\n                tagDepthStack.pop();\n            }\n            const depth = tagDepthStack.length;\n            tagDepthStack.push(heading.tagName);\n            return {\n                depth,\n                name: heading.innerText,\n                target: heading,\n            };\n        });\n    }\n}\n", "import {\n    getEditableDescendants,\n    getEmbeddedProps,\n    useEditableDescendants,\n} from \"@html_editor/others/embedded_component_utils\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { Component, useEffect, useExternalListener, useState } from \"@odoo/owl\";\n\nconst sessionStorage = browser.sessionStorage;\nexport class EmbeddedToggleBlockComponent extends Component {\n    static template = \"html_editor.EmbeddedToggleBlock\";\n    static props = {\n        host: { type: Object },\n        toggleBlockId: { type: String },\n    };\n\n    setup() {\n        useEditableDescendants(this.props.host);\n        this.state = useState({\n            showContent: sessionStorage.getItem(this.toggleStorageKey) === \"true\",\n        });\n        this.neutralRestoreSelection = () => {};\n        this.restoreSelection = this.neutralRestoreSelection;\n        useExternalListener(this.props.host, \"forceToggle\", this.onToggle);\n        useEffect(\n            () => {\n                this.restoreSelection();\n                this.restoreSelection = this.neutralRestoreSelection;\n            },\n            () => [this.restoreSelection]\n        );\n    }\n\n    get toggleStorageKey() {\n        return `html_editor.ToggleBlock${this.props.toggleBlockId}.showContent`;\n    }\n\n    onToggle(ev) {\n        let { showContent, restoreSelection } = ev.detail ?? {};\n        showContent ??= !this.state.showContent;\n        restoreSelection ??= this.neutralRestoreSelection;\n        if (this.state.showContent !== showContent) {\n            this.restoreSelection = restoreSelection;\n            this.state.showContent = showContent;\n            sessionStorage.setItem(this.toggleStorageKey, this.state.showContent);\n        } else {\n            restoreSelection();\n        }\n    }\n}\n\nexport const toggleBlockEmbedding = {\n    name: \"toggleBlock\",\n    Component: EmbeddedToggleBlockComponent,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n    getEditableDescendants: getEditableDescendants,\n};\n", "import { getEmbeddedProps } from \"@html_editor/others/embedded_component_utils\";\nimport { getVideoUrl } from \"@html_editor/utils/url\";\nimport { Component } from \"@odoo/owl\";\n\nexport class ReadonlyEmbeddedVideoComponent extends Component {\n    static template = \"html_editor.EmbeddedVideo\";\n    static props = {\n        platform: { type: String },\n        videoId: { type: String },\n        params: { type: Object, optional: true },\n    };\n\n    get url() {\n        return getVideoUrl(this.props.platform, this.props.videoId, this.props.params).toString();\n    }\n}\n\nexport const readonlyVideoEmbedding = {\n    name: \"video\",\n    Component: ReadonlyEmbeddedVideoComponent,\n    getProps: (host) => ({ ...getEmbeddedProps(host) }),\n};\n", "export const BASE_CONTAINER_CLASS = \"o-paragraph\";\n\nexport const SUPPORTED_BASE_CONTAINER_NAMES = [\"P\", \"DIV\"];\n\n/**\n * @param {string} [nodeName] @see SUPPORTED_BASE_CONTAINER_NAMES\n *                 will return the global selector if nodeName is not specified.\n * @returns {string} selector for baseContainers.\n */\nexport function getBaseContainerSelector(nodeName) {\n    if (!nodeName) {\n        return baseContainerGlobalSelector;\n    }\n    nodeName = SUPPORTED_BASE_CONTAINER_NAMES.includes(nodeName) ? nodeName : \"P\";\n    let suffix = \"\";\n    if (nodeName !== \"P\") {\n        suffix = `.${BASE_CONTAINER_CLASS}`;\n    }\n    return `${nodeName}${suffix}`;\n}\n\nexport const baseContainerGlobalSelector = `:is(${SUPPORTED_BASE_CONTAINER_NAMES.map((name) =>\n    getBaseContainerSelector(name)\n).join(\",\")})`;\n\n/**\n * Create a new baseContainer element.\n *\n * @param {string} nodeName @see SUPPORTED_BASE_CONTAINER_NAMES\n * @param {Document} [document] Used to create new baseContainer elements.\n *                   For iframes, preferably use the iframe document.\n *                   Fallbacks to the window document if possible and unspecified.\n *                   Has to be specified otherwise.\n * @returns {HTMLElement}\n */\nexport function createBaseContainer(nodeName, document) {\n    if (!document && window) {\n        document = window.document;\n    }\n    nodeName = nodeName && SUPPORTED_BASE_CONTAINER_NAMES.includes(nodeName) ? nodeName : \"P\";\n    const el = document.createElement(nodeName);\n    if (nodeName !== \"P\") {\n        el.className = BASE_CONTAINER_CLASS;\n    }\n    return el;\n}\n", "import { closestPath, findNode } from \"./dom_traversal\";\n\nconst blockTagNames = [\n    \"ADDRESS\",\n    \"ARTICLE\",\n    \"ASIDE\",\n    \"BLOCKQUOTE\",\n    \"DETAILS\",\n    \"DIALOG\",\n    \"DD\",\n    \"DIV\",\n    \"DL\",\n    \"DT\",\n    \"FIELDSET\",\n    \"FIGCAPTION\",\n    \"FIGURE\",\n    \"FOOTER\",\n    \"FORM\",\n    \"H1\",\n    \"H2\",\n    \"H3\",\n    \"H4\",\n    \"H5\",\n    \"H6\",\n    \"HEADER\",\n    \"HGROUP\",\n    \"HR\",\n    \"LI\",\n    \"MAIN\",\n    \"NAV\",\n    \"OL\",\n    \"P\",\n    \"PRE\",\n    \"SECTION\",\n    \"TABLE\",\n    \"UL\",\n    // The following elements are not in the W3C list, for some reason.\n    \"SELECT\",\n    \"OPTION\",\n    \"TR\",\n    \"TD\",\n    \"TBODY\",\n    \"THEAD\",\n    \"TH\",\n];\n\nconst computedStyleDisplayCache = new WeakMap();\n\n/**\n * Return true if the given node is a block-level element, false otherwise.\n *\n * @param node\n */\nexport function isBlock(node) {\n    if (!node || node.nodeType !== Node.ELEMENT_NODE) {\n        return false;\n    }\n    const tagName = node.nodeName.toUpperCase();\n    if (tagName === \"BR\") {\n        // A <br> is always inline but getComputedStyle(br).display mistakenly\n        // returns 'block' if its parent is display:flex (at least on Chrome and\n        // Firefox (Linux)). Browsers normally support setting a <br>'s display\n        // property to 'none' but any other change is not supported. Therefore\n        // it is safe to simply declare that a <br> is never supposed to be a\n        // block.\n        return false;\n    }\n    // The node might not be in the DOM, in which case it has no CSS values.\n    if (!node.isConnected) {\n        return blockTagNames.includes(tagName);\n    }\n    // We won't call `getComputedStyle(node).display` more than once per node.\n    let display = computedStyleDisplayCache.get(node);\n    if (display === undefined) {\n        const style = node.ownerDocument.defaultView.getComputedStyle(node);\n        display = style.display;\n        computedStyleDisplayCache.set(node, display);\n    }\n    if (display) {\n        return !display.includes(\"inline\") && display !== \"contents\";\n    }\n    return blockTagNames.includes(tagName);\n}\n\nexport function closestBlock(node) {\n    return findNode(closestPath(node), (node) => isBlock(node));\n}\n", "/**\n * Add origin to relative img src.\n * @param {Document} doc\n * @param {string} origin\n */\nfunction prependOriginToImages(doc, origin) {\n    doc.querySelectorAll(\"img\").forEach((img) => {\n        const src = img.getAttribute(\"src\");\n        if (src && !/^(http|\\/\\/|data:)/.test(src)) {\n            img.src = origin + (src.startsWith(\"/\") ? src : \"/\" + src);\n        }\n    });\n}\n\n/**\n * Fills clipboard data, also with the\n * application/vnd.odoo.odoo-editor mimetype so that it can recognized\n * on paste inside an editor.\n * @param {ClipboardEvent} ev copy event\n * @param {DocumentFragment} clonedContents\n */\nexport function fillClipboardData(ev, clonedContents) {\n    const doc = ev.target.ownerDocument;\n    const dataHtmlElement = doc.createElement(\"data\");\n    dataHtmlElement.append(clonedContents);\n    prependOriginToImages(dataHtmlElement, doc.defaultView.location.origin);\n    const htmlContent = dataHtmlElement.innerHTML;\n    ev.clipboardData.setData(\"text/html\", htmlContent);\n    ev.clipboardData.setData(\"application/vnd.odoo.odoo-editor\", htmlContent);\n}\n", "import { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { isColorGradient } from \"@web/core/utils/colors\";\nimport { isElement } from \"./dom_info\";\n\nexport const COLOR_PALETTE_COMPATIBILITY_COLOR_NAMES = [\n    \"primary\",\n    \"secondary\",\n    \"alpha\",\n    \"beta\",\n    \"gamma\",\n    \"delta\",\n    \"epsilon\",\n    \"success\",\n    \"info\",\n    \"warning\",\n    \"danger\",\n];\n\n/**\n * Colors of the default palette, used for substitution in shapes/illustrations.\n * key: number of the color in the palette (ie, o-color-<1-5>)\n * value: color hex code\n */\nexport const DEFAULT_PALETTE = {\n    1: \"#3AADAA\",\n    2: \"#7C6576\",\n    3: \"#F6F6F6\",\n    4: \"#FFFFFF\",\n    5: \"#383E45\",\n};\n\n/**\n * These constants are colors that can be edited by the user when using\n * web_editor in a website context. We keep track of them so that color\n * palettes and their preview elements can always have the right colors\n * displayed even if website has redefined the colors during an editing\n * session.\n *\n * @type {string[]}\n */\nexport const EDITOR_COLOR_CSS_VARIABLES = [...COLOR_PALETTE_COMPATIBILITY_COLOR_NAMES];\n\n// o-cc and o-colors\nfor (let i = 1; i <= 5; i++) {\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-color-${i}`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-bg`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-bg-gradient`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-headings`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-text`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-btn-primary`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-btn-primary-text`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-btn-secondary`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-btn-secondary-text`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-btn-primary-border`);\n    EDITOR_COLOR_CSS_VARIABLES.push(`o-cc${i}-btn-secondary-border`);\n}\n\n// Grays\nfor (let i = 100; i <= 900; i += 100) {\n    EDITOR_COLOR_CSS_VARIABLES.push(`${i}`);\n}\n\n// Black, white and their opacity variants.\n// These variables are necessary to prevent the colorpicker from being affected\n// by the backend \"Dark Mode\".\nEDITOR_COLOR_CSS_VARIABLES.push(\n    \"black\",\n    \"black-15\",\n    \"black-25\",\n    \"black-50\",\n    \"black-75\",\n    \"white\",\n    \"white-25\",\n    \"white-50\",\n    \"white-75\",\n    \"white-85\"\n);\n\n/**\n * @param {string|number} name\n * @returns {boolean}\n */\nexport function isColorCombinationName(name) {\n    const number = parseInt(name);\n    return !isNaN(number) && number % 100 !== 0;\n}\n\nexport const TEXT_CLASSES_REGEX =\n    /\\btext-(primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black|reset|gradient|opacity-\\d{1,3}|o-[^\\s]+|\\d+)\\b/;\nexport const BG_CLASSES_REGEX = /\\bbg-[^\\s]*\\b/;\nexport const COLOR_COMBINATION_CLASSES_REGEX = /\\bo_cc[0-9]+\\b/g;\n\n/**\n * Returns true if the given element has a visible color applied\n * by `TEXT_CLASSES_REGEX` or `BG_CLASSES_REGEX`\n *\n * @param {Element} element\n * @param {string} mode 'color' or 'backgroundColor'\n * @returns {boolean}\n */\nexport function hasTextColorClass(element, mode) {\n    if (!element || !isElement(element)) {\n        return false;\n    }\n    const classRegex = mode === \"color\" ? TEXT_CLASSES_REGEX : BG_CLASSES_REGEX;\n    const parent = element.parentNode;\n    return (\n        classRegex.test(element.className) &&\n        (!parent || getComputedStyle(element)[mode] !== getComputedStyle(parent)[mode])\n    );\n}\n\n/**\n * Returns true if the given element has a visible color (fore- or\n * -background depending on the given mode).\n *\n * @param {Element} element\n * @param {string} mode 'color' or 'backgroundColor'\n * @returns {boolean}\n */\nexport function hasColor(element, mode) {\n    const style = element.style;\n    const parent = element.parentNode;\n    if (element.classList.contains(\"btn\")) {\n        // Ignore style applied on buttons from color detection\n        return false;\n    }\n    if (isColorGradient(style[\"background-image\"])) {\n        if (element.classList.contains(\"text-gradient\")) {\n            if (mode === \"color\") {\n                return true;\n            }\n        } else {\n            if (mode !== \"color\") {\n                return true;\n            }\n        }\n    }\n    return (\n        (style[mode] &&\n            style[mode] !== \"inherit\" &&\n            (!parent || style[mode] !== parent.style[mode])) ||\n        hasTextColorClass(element, mode)\n    );\n}\n\n/**\n * Returns true if any given nodes has a visible color (fore- or\n * -background depending on the given mode).\n *\n * @param {array} nodes\n * @param {string} mode 'color' or 'backgroundColor'\n * @returns {boolean}\n */\nexport function hasAnyNodesColor(nodes, mode) {\n    for (const node of nodes) {\n        if (hasColor(closestElement(node), mode)) {\n            return true;\n        }\n    }\n    return false;\n}\n\nexport function getTextColorOrClass(node) {\n    if (!node) {\n        return null;\n    }\n    if (node.style.color) {\n        return { type: \"style\", value: node.style.color };\n    }\n    const textColorClass = [...node.classList].find((cls) => TEXT_CLASSES_REGEX.test(cls));\n    if (textColorClass) {\n        return { type: \"class\", value: textColorClass };\n    }\n    return null;\n}\n", "export const CTYPES = {\n    // Short for CONTENT_TYPES\n    // Inline group\n    CONTENT: 1,\n    SPACE: 2,\n\n    // Block group\n    BLOCK_OUTSIDE: 4,\n    BLOCK_INSIDE: 8,\n\n    // Br group\n    BR: 16,\n};\nexport function ctypeToString(ctype) {\n    return Object.keys(CTYPES).find((key) => CTYPES[key] === ctype);\n}\nexport const CTGROUPS = {\n    // Short for CONTENT_TYPE_GROUPS\n    INLINE: CTYPES.CONTENT | CTYPES.SPACE,\n    BLOCK: CTYPES.BLOCK_OUTSIDE | CTYPES.BLOCK_INSIDE,\n    BR: CTYPES.BR,\n};\n", "import { closestBlock, isBlock } from \"./blocks\";\nimport {\n    isEmptyTextNode,\n    isParagraphRelatedElement,\n    isShrunkBlock,\n    isVisible,\n    nextLeaf,\n    previousLeaf,\n} from \"./dom_info\";\nimport { callbacksForCursorUpdate } from \"./selection\";\nimport { isEmptyBlock, isPhrasingContent } from \"../utils/dom_info\";\nimport { childNodes, descendants } from \"./dom_traversal\";\nimport { childNodeIndex, DIRECTIONS } from \"./position\";\nimport {\n    baseContainerGlobalSelector,\n    createBaseContainer,\n} from \"@html_editor/utils/base_container\";\n\n/** @typedef {import(\"@html_editor/core/selection_plugin\").Cursors} Cursors */\n\n/**\n * Take a node and unwrap all of its block contents recursively. All blocks\n * (except for firstChilds) are preceded by a <br> in order to preserve the line\n * breaks.\n *\n * @param {Node} node\n */\nexport function makeContentsInline(node) {\n    const document = node.ownerDocument;\n    let childIndex = 0;\n    for (const child of node.childNodes) {\n        if (isBlock(child)) {\n            if (childIndex && isParagraphRelatedElement(child)) {\n                child.before(document.createElement(\"br\"));\n            }\n            for (const grandChild of child.childNodes) {\n                child.before(grandChild);\n                makeContentsInline(grandChild);\n            }\n            child.remove();\n        }\n        childIndex += 1;\n    }\n}\n\n/**\n * Wrap inline children nodes in Blocks, optionally updating cursors for\n * later selection restore. A paragraph is used for phrasing node, and a div\n * is used otherwise.\n *\n * @param {HTMLElement} element - block element\n * @param {Cursors} [cursors]\n */\nexport function wrapInlinesInBlocks(\n    element,\n    { baseContainerNodeName = \"P\", cursors = { update: () => {} } } = {}\n) {\n    // Helpers to manipulate preserving selection.\n    const wrapInBlock = (node, cursors) => {\n        const block = isPhrasingContent(node)\n            ? createBaseContainer(baseContainerNodeName, node.ownerDocument)\n            : node.ownerDocument.createElement(\"DIV\");\n        cursors.update(callbacksForCursorUpdate.append(block, node));\n        cursors.update(callbacksForCursorUpdate.before(node, block));\n        if (node.nextSibling) {\n            const sibling = node.nextSibling;\n            node.remove();\n            sibling.before(block);\n        } else {\n            const parent = node.parentElement;\n            node.remove();\n            parent.append(block);\n        }\n        block.append(node);\n        return block;\n    };\n    const appendToCurrentBlock = (currentBlock, node, cursors) => {\n        if (currentBlock.matches(baseContainerGlobalSelector) && !isPhrasingContent(node)) {\n            const block = currentBlock.ownerDocument.createElement(\"DIV\");\n            cursors.update(callbacksForCursorUpdate.before(currentBlock, block));\n            currentBlock.before(block);\n            for (const child of childNodes(currentBlock)) {\n                cursors.update(callbacksForCursorUpdate.append(block, child));\n                block.append(child);\n            }\n            cursors.update(callbacksForCursorUpdate.remove(currentBlock));\n            currentBlock.remove();\n            currentBlock = block;\n        }\n        cursors.update(callbacksForCursorUpdate.append(currentBlock, node));\n        currentBlock.append(node);\n        return currentBlock;\n    };\n    const removeNode = (node, cursors) => {\n        cursors.update(callbacksForCursorUpdate.remove(node));\n        node.remove();\n    };\n\n    const children = childNodes(element);\n    const visibleNodes = new Set(children.filter(isVisible));\n\n    let currentBlock;\n    let shouldBreakLine = true;\n    for (const node of children) {\n        if (isBlock(node)) {\n            shouldBreakLine = true;\n        } else if (!visibleNodes.has(node)) {\n            removeNode(node, cursors);\n        } else if (node.nodeName === \"BR\") {\n            if (shouldBreakLine) {\n                wrapInBlock(node, cursors);\n            } else {\n                // BR preceded by inline content: discard it and make sure\n                // next inline goes in a new Block\n                removeNode(node, cursors);\n                shouldBreakLine = true;\n            }\n        } else if (shouldBreakLine) {\n            currentBlock = wrapInBlock(node, cursors);\n            shouldBreakLine = false;\n        } else {\n            currentBlock = appendToCurrentBlock(currentBlock, node, cursors);\n        }\n    }\n}\n\nexport function unwrapContents(node) {\n    const contents = childNodes(node);\n    for (const child of contents) {\n        node.parentNode.insertBefore(child, node);\n    }\n    node.parentNode.removeChild(node);\n    return contents;\n}\n\n/**\n * Removes the specified class names from the given element.  If the element has\n * no more class names after removal, the \"class\" attribute is removed.\n *\n * @param {Element} element - The element from which to remove the class names.\n * @param {...string} classNames - The class names to be removed.\n */\nexport function removeClass(element, ...classNames) {\n    const classNamesSet = new Set(classNames);\n    if ([...element.classList].every((className) => classNamesSet.has(className))) {\n        element.removeAttribute(\"class\");\n    } else {\n        element.classList.remove(...classNames);\n    }\n}\n\nexport function removeStyle(element, ...styleProperties) {\n    const propsToRemoveSet = new Set(styleProperties);\n    if ([...element.style].every((prop) => propsToRemoveSet.has(prop))) {\n        element.removeAttribute(\"style\");\n    } else {\n        styleProperties.forEach((prop) => element.style.removeProperty(prop));\n    }\n}\n\n/**\n * Add a BR in the given node if its closest ancestor block has nothing to make\n * it visible, and/or add a zero-width space in the given node if it's an empty\n * inline so the cursor can stay in it.\n *\n * @param {HTMLElement} el\n * @returns {Object} { br: the inserted <br> if any,\n *                     zws: the inserted zero-width space if any }\n */\nexport function fillEmpty(el) {\n    const document = el.ownerDocument;\n    if (!isBlock(el) && !isVisible(el) && !el.hasAttribute(\"data-oe-zws-empty-inline\")) {\n        const zws = document.createTextNode(\"\\u200B\");\n        el.appendChild(zws);\n        el.setAttribute(\"data-oe-zws-empty-inline\", \"\");\n        const previousSibling = el.previousSibling;\n        if (previousSibling && previousSibling.nodeName === \"BR\") {\n            previousSibling.remove();\n        }\n        return { zws };\n    } else {\n        // If a ZWS was inserted, there is no need for a <br>.\n        return fillShrunkPhrasingParent(el);\n    }\n}\n\n/**\n * Add a BR in a shrunk phrasing parent to make it visible.\n * A shrunk block is assumed to be a phrasing parent, and the inserted\n * <br> must be wrapped in a paragraph by the caller if necessary.\n *\n * @param {HTMLElement} el\n * @returns {Object} { br: the inserted <br> if any }\n */\nexport function fillShrunkPhrasingParent(el) {\n    const document = el.ownerDocument;\n    const fillers = {};\n    const blockEl = closestBlock(el);\n    if (isShrunkBlock(blockEl)) {\n        const br = document.createElement(\"br\");\n        blockEl.appendChild(br);\n        fillers.br = br;\n    }\n    return fillers;\n}\n\n/**\n * Removes a trailing BR if it is unnecessary:\n * in a non-empty block, if the last childNode is a BR and its previous sibling\n * is not a BR, remove the BR.\n *\n * @param {HTMLElement} el\n * @param {Array} predicates exceptions where a trailing BR should not be removed\n * @returns {HTMLElement|undefined} the removed br, if any\n */\nexport function cleanTrailingBR(el, predicates = []) {\n    const candidate = el?.lastChild;\n    if (\n        candidate?.nodeName === \"BR\" &&\n        candidate.previousSibling?.nodeName !== \"BR\" &&\n        !isEmptyBlock(el) &&\n        !predicates.some((predicate) => predicate(candidate))\n    ) {\n        candidate.remove();\n        return candidate;\n    }\n}\n\n/**\n * Wrapper for classList.toggle that removes the class attribute if the\n * element has no class name after the toggle.\n *\n * @param {Element} element\n * @param {string} className\n * @param {boolean} [force]\n */\nexport function toggleClass(element, className, force) {\n    element.classList.toggle(className, force);\n    if (!element.className) {\n        element.removeAttribute(\"class\");\n    }\n}\n\n/**\n * Remove all occurrences of a character from a text node and optionally update\n * cursors for later selection restore.\n *\n * In web_editor the text nodes used to be replaced by new ones with the updated\n * text rather than just changing the text content of the node because it\n * creates different mutations and it used to break the tour system. In\n * html_editor the text content is changed instead because other plugins rely on\n * the reference to the text node.\n *\n * @param {Node} node text node\n * @param {String} char character to remove (string of length 1)\n * @param {Cursors} [cursors]\n */\nexport function cleanTextNode(node, char, cursors) {\n    const removedIndexes = [];\n    node.textContent = node.textContent.replaceAll(char, (_, offset) => {\n        removedIndexes.push(offset);\n        return \"\";\n    });\n    if (isEmptyTextNode(node)) {\n        cursors?.update(callbacksForCursorUpdate.remove(node));\n        node.remove();\n    } else {\n        cursors?.update((cursor) => {\n            if (cursor.node === node) {\n                cursor.offset -= removedIndexes.filter((index) => cursor.offset > index).length;\n            }\n        });\n    }\n}\n\n/**\n * Splits a text node in two parts.\n * If the split occurs at the beginning or the end, the text node stays\n * untouched and unsplit. If a split actually occurs, the original text node\n * still exists and become the right part of the split.\n *\n * Note: if split after or before whitespace, that whitespace may become\n * invisible, it is up to the caller to replace it by nbsp if needed.\n *\n * @param {Text} textNode\n * @param {number} offset\n * @param {boolean} originalNodeSide Whether the original node ends up on left\n * or right after the split\n * @returns {number} The parentOffset if the cursor was between the two text\n *          node parts after the split.\n */\nexport function splitTextNode(textNode, offset, originalNodeSide = DIRECTIONS.RIGHT) {\n    const document = textNode.ownerDocument;\n    let parentOffset = childNodeIndex(textNode);\n\n    if (offset > 0) {\n        parentOffset++;\n\n        if (offset < textNode.length) {\n            const left = textNode.nodeValue.substring(0, offset);\n            const right = textNode.nodeValue.substring(offset);\n            if (originalNodeSide === DIRECTIONS.LEFT) {\n                const newTextNode = document.createTextNode(right);\n                textNode.after(newTextNode);\n                textNode.nodeValue = left;\n            } else {\n                const newTextNode = document.createTextNode(left);\n                textNode.before(newTextNode);\n                textNode.nodeValue = right;\n            }\n        }\n    }\n    return parentOffset;\n}\n\n/**\n * Remove invisible whitespace from an element and adapt the given cursors\n * accordingly if any.\n *\n * Note (TODO): in the future, this function should use the mechanism used by\n * `enforceWhitespace` but doing so would require a little overhaul of it and of\n * `getState`/`restoreState` to isolate the part that identifies invisible\n * whitespace.\n *\n * @param {Element} el\n * @param {import(\"@html_editor/core/selection_plugin\").Cursors} [cursors]\n */\nexport function removeInvisibleWhitespace(el, cursors) {\n    const [countLeadingWhitespace, countTrailingWhitespace] = [/^\\s+/, /\\s+$/].map(\n        (regex) => (node) => node?.textContent.match(regex)?.[0]?.length || 0\n    );\n    const isInlineElement = (node) => node?.nodeType === Node.ELEMENT_NODE && !isBlock(node);\n    const textChildren = descendants(el).filter((child) => child.nodeType === Node.TEXT_NODE);\n    let removedTrailingSpaceBefore = false;\n    let index = 0;\n    for (const child of textChildren) {\n        let leadingWhitespace = countLeadingWhitespace(child);\n        let trailingWhitespace = countTrailingWhitespace(child);\n        const previous = previousLeaf(child, el);\n        if (\n            leadingWhitespace &&\n            previous &&\n            (isInlineElement(child.previousSibling) || removedTrailingSpaceBefore)\n        ) {\n            // `<span>a</span>\\n   b` shows as `<span>a</span> b`\n            leadingWhitespace -= 1; // Keep one space.\n        } else if (\n            trailingWhitespace &&\n            index !== textChildren.length - 1 &&\n            isInlineElement(child.nextSibling) &&\n            !countTrailingWhitespace(nextLeaf(child, el))\n        ) {\n            // `a\\n   <span>\\n   b\\n</span>` shows as `a <span>b</span>`\n            trailingWhitespace -= 1; // Keep one space.\n        }\n        removedTrailingSpaceBefore = !!trailingWhitespace;\n        cursors?.shiftOffset(child, -leadingWhitespace);\n        child.textContent = child.textContent\n            .substring(\n                leadingWhitespace,\n                child.textContent.length - trailingWhitespace || leadingWhitespace\n            )\n            .replace(/^\\s+/, \" \")\n            .replace(/\\s+$/, \" \");\n        if (!child.textContent) {\n            child.remove();\n        }\n        index += 1;\n    }\n}\n", "import { baseContainerGlobalSelector } from \"./base_container\";\nimport { closestBlock, isBlock } from \"./blocks\";\nimport { childNodes, closestElement, firstLeaf, lastLeaf } from \"./dom_traversal\";\nimport { DIRECTIONS, nodeSize } from \"./position\";\n\nexport function isEmpty(el) {\n    if (isProtecting(el) || isProtected(el)) {\n        return false;\n    }\n    const content = el.innerHTML.trim();\n    if (content === \"\" || content === \"<br>\") {\n        return true;\n    }\n    return false;\n}\n\nexport function isEmptyTextNode(node) {\n    if (node.nodeType !== Node.TEXT_NODE) {\n        return false;\n    }\n    if (!node.textContent) {\n        return true;\n    }\n    const trimmedContent = node.textContent.trim();\n    if (!trimmedContent) {\n        // Only `\\n` is considered as empty\n        if (node.textContent.includes(\"\\n\")) {\n            return true;\n        }\n        // Only spaces is not considered as empty\n        // we technically can apply styles on spaces\n        if (node.textContent) {\n            return false;\n        }\n    }\n    return !trimmedContent;\n}\n\n/**\n * Return true if the given node appears bold. The node is considered to appear\n * bold if its font weight is bigger than 500 (eg.: Heading 1), or if its font\n * weight is bigger than that of its closest block.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isBold(node) {\n    const fontWeight = +getComputedStyle(closestElement(node)).fontWeight;\n    const referenceElement = closestElement(\n        node,\n        (el) => isBlock(el) || +getComputedStyle(el).fontWeight !== fontWeight\n    );\n    return fontWeight > 500 || fontWeight > +getComputedStyle(referenceElement).fontWeight;\n}\n\n/**\n * Return true if the given node appears italic.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isItalic(node) {\n    return getComputedStyle(closestElement(node)).fontStyle === \"italic\";\n}\n\n/**\n * Return true if the given node appears underlined.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isUnderline(node) {\n    let parent = closestElement(node);\n    while (parent) {\n        if (getComputedStyle(parent).textDecorationLine.includes(\"underline\")) {\n            return true;\n        }\n        parent = parent.parentElement;\n    }\n    return false;\n}\n\n/**\n * Return true if the given node appears struck through.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isStrikeThrough(node) {\n    let parent = closestElement(node);\n    while (parent) {\n        if (\n            !parent.classList.contains(\"o_checked\") &&\n            getComputedStyle(parent).textDecorationLine.includes(\"line-through\")\n        ) {\n            return true;\n        }\n        parent = parent.parentElement;\n    }\n    return false;\n}\n\n/**\n * Return true if the given node font-size is equal to `props.size`.\n *\n * @param {Object} props\n * @param {Node} props.node A node to compare the font-size against.\n * @param {String} props.size The font-size value of the node that will be\n *     checked against.\n * @returns {boolean}\n */\nexport function isFontSize(node, props) {\n    const element = closestElement(node);\n    return getComputedStyle(element)[\"font-size\"] === props.size;\n}\n\n/**\n * Return true if the given node classlist contains `props.className`.\n *\n * @param {Object} props\n * @param {Node} node A node to compare the font-size against.\n * @param {String} props.className The name of the class.\n * @returns {boolean}\n */\nexport function hasClass(node, props) {\n    const element = closestElement(node);\n    return element.classList.contains(props.className);\n}\n\n/**\n * Return true if the given node appears in a different direction than that of\n * the editable ('ltr' or 'rtl').\n *\n * Note: The direction of the editable is set on its \"dir\" attribute, to the\n * value of the \"direction\" option on instantiation of the editor.\n *\n * @param {Node} node\n * @param {Element} editable\n * @returns {boolean}\n */\nexport function isDirectionSwitched(node, editable) {\n    const defaultDirection = editable.getAttribute(\"dir\") || \"ltr\";\n    return getComputedStyle(closestElement(node)).direction !== defaultDirection;\n}\n\n// /**\n//  * Return true if the given node is a row element.\n//  */\nexport function isRow(node) {\n    return [\"TH\", \"TD\"].includes(node.tagName);\n}\n\nexport function isZWS(node) {\n    return node && node.textContent === \"\\u200B\";\n}\n\n/**\n * Returns true if the given node is in a PRE context for whitespace handling.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isInPre(node) {\n    const element = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;\n    return (\n        !!element &&\n        (!!element.closest(\"pre\") ||\n            getComputedStyle(element).getPropertyValue(\"white-space\") === \"pre\")\n    );\n}\n\nexport const ZERO_WIDTH_CHARS = [\"\\u200b\", \"\\ufeff\"];\n\nexport const whitespace = `[^\\\\S\\\\u00A0\\\\u0009\\\\ufeff]`; // for formatting (no \"real\" content) (TODO: 0009 shouldn't be included)\nconst whitespaceRegex = new RegExp(`^${whitespace}*$`);\nexport function isWhitespace(value) {\n    const str = typeof value === \"string\" ? value : value.nodeValue;\n    return whitespaceRegex.test(str);\n}\n\n// eslint-disable-next-line no-control-regex\nconst visibleCharRegex = /[^\\s\\u200b]|[\\u00A0\\u0009]$/; // contains at least a char that is always visible (TODO: 0009 shouldn't be included)\nexport function isVisibleTextNode(testedNode) {\n    if (!testedNode || !testedNode.length || testedNode.nodeType !== Node.TEXT_NODE) {\n        return false;\n    }\n    if (isProtected(testedNode)) {\n        return true;\n    }\n    if (\n        visibleCharRegex.test(testedNode.textContent) ||\n        (isInPre(testedNode) && isWhitespace(testedNode))\n    ) {\n        return true;\n    }\n    if (ZERO_WIDTH_CHARS.includes(testedNode.textContent)) {\n        return false; // a ZW(NB)SP is always invisible, regardless of context.\n    }\n    // The following assumes node is made entirely of whitespace and is not\n    // preceded of followed by a block.\n    // Find out contiguous preceding and following text nodes\n    let preceding;\n    let following;\n    // Control variable to know whether the current node has been found\n    let foundTestedNode;\n    const currentNodeParentBlock = closestBlock(testedNode);\n    if (!currentNodeParentBlock) {\n        return false;\n    }\n    const nodeIterator = document.createNodeIterator(currentNodeParentBlock);\n    for (let node = nodeIterator.nextNode(); node; node = nodeIterator.nextNode()) {\n        if (node.nodeType === Node.TEXT_NODE) {\n            // If we already found the tested node, the current node is the\n            // contiguous following, and we can stop looping\n            // If the current node is the tested node, mark it as found and\n            // continue.\n            // If we haven't reached the tested node, overwrite the preceding\n            // node.\n            if (foundTestedNode) {\n                following = node;\n                break;\n            } else if (testedNode === node) {\n                foundTestedNode = true;\n            } else {\n                preceding = node;\n            }\n        } else if (isBlock(node)) {\n            // If we found the tested node, then the following node is irrelevant\n            // If we didn't, then the current preceding node is irrelevant\n            if (foundTestedNode) {\n                break;\n            } else {\n                preceding = null;\n            }\n        } else if (foundTestedNode && !isWhitespace(node)) {\n            // <block>space<inline>text</inline></block> -> space is visible\n            following = node;\n            break;\n        }\n    }\n    while (following && !visibleCharRegex.test(following.textContent)) {\n        following = following.nextSibling;\n    }\n    // Missing preceding or following: invisible.\n    // Preceding or following not in the same block as tested node: invisible.\n    if (\n        !(preceding && following) ||\n        currentNodeParentBlock !== closestBlock(preceding) ||\n        currentNodeParentBlock !== closestBlock(following)\n    ) {\n        return false;\n    }\n    // Preceding is whitespace or following is whitespace: invisible\n    return visibleCharRegex.test(preceding.textContent);\n}\n\n/**\n * Returns whether the given node is a element that could be considered to be\n * removed by itself = self closing tags.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nconst selfClosingElementTags = [\"BR\", \"IMG\", \"INPUT\", \"T\", \"HR\"];\nexport function isSelfClosingElement(node) {\n    return node && selfClosingElementTags.includes(node.nodeName);\n}\n\n/**\n * Returns whether removing the given node from the DOM will have a visible\n * effect or not.\n *\n * Note: TODO this is not handling all cases right now, just the ones the\n * caller needs at the moment. For example a space text node between two inlines\n * will always return 'true' while it is sometimes invisible.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isVisible(node) {\n    return (\n        !!node &&\n        ((node.nodeType === Node.TEXT_NODE && isVisibleTextNode(node)) ||\n            isSelfClosingElement(node) ||\n            // @todo: handle it in resources?\n            isMediaElement(node) ||\n            hasVisibleContent(node) ||\n            isProtecting(node) ||\n            isEmbeddedComponent(node))\n    );\n}\nexport function hasVisibleContent(node) {\n    return (node ? childNodes(node) : []).some((n) => isVisible(n));\n}\n\nexport function isButton(node) {\n    if (!node || node.nodeType !== Node.ELEMENT_NODE) {\n        return false;\n    }\n    return node.nodeName === \"BUTTON\" || node.classList.contains(\"btn\");\n}\n\nexport function isZwnbsp(node) {\n    return node?.nodeType === Node.TEXT_NODE && node.textContent === \"\\ufeff\";\n}\n\nexport function isTangible(node) {\n    return isVisible(node) || isZwnbsp(node) || hasTangibleContent(node);\n}\n\nexport function hasTangibleContent(node) {\n    return (node ? childNodes(node) : []).some((n) => isTangible(n));\n}\n\nexport const isNotEditableNode = (node) =>\n    node.getAttribute &&\n    node.getAttribute(\"contenteditable\") &&\n    node.getAttribute(\"contenteditable\").toLowerCase() === \"false\";\n\nconst iconTags = [\"I\", \"SPAN\"];\n// @todo @phoenix: move the specific part in a proper plugin.\nexport const iconClasses = [\"fa\", \"fab\", \"fad\", \"far\", \"oi\"];\n\nexport const ICON_SELECTOR = iconTags\n    .map((tag) => iconClasses.map((cls) => `${tag}.${cls}`).join(\", \"))\n    .join(\", \");\n\nexport const MEDIA_SELECTOR = `${ICON_SELECTOR} , .o_image, .media_iframe_video`;\n\nexport const EDITABLE_MEDIA_CLASS = \"o_editable_media\";\n\n/**\n * Indicates if the given node is an icon element.\n *\n * @see ICON_SELECTOR\n * @param {?Node} [node]\n * @returns {boolean}\n */\nexport function isIconElement(node) {\n    return !!(\n        node &&\n        iconTags.includes(node.nodeName) &&\n        iconClasses.some((cls) => node.classList.contains(cls))\n    );\n}\n// @todo @phoenix: move the specific part in a proper plugin.\nexport function isMediaElement(node) {\n    return (\n        isIconElement(node) ||\n        (node.classList &&\n            (node.classList.contains(\"o_image\") ||\n                node.classList.contains(\"media_iframe_video\"))) ||\n        node.nodeName === \"CANVAS\"\n    );\n}\n\n// See https://developer.mozilla.org/en-US/docs/Web/HTML/Content_categories#phrasing_content\nconst phrasingTagNames = new Set([\n    \"ABBR\",\n    \"AUDIO\",\n    \"B\",\n    \"BDI\",\n    \"BDO\",\n    \"BR\",\n    \"BUTTON\",\n    \"CANVAS\",\n    \"CITE\",\n    \"CODE\",\n    \"DATA\",\n    \"DATALIST\",\n    \"DFN\",\n    \"EM\",\n    \"EMBED\",\n    \"I\",\n    \"IFRAME\",\n    \"IMG\",\n    \"INPUT\",\n    \"KBD\",\n    \"LABEL\",\n    \"MARK\",\n    \"MATH\",\n    \"METER\",\n    \"NOSCRIPT\",\n    \"OBJECT\",\n    \"OUTPUT\",\n    \"PICTURE\",\n    \"PROGRESS\",\n    \"Q\",\n    \"RUBY\",\n    \"S\",\n    \"SAMP\",\n    \"SCRIPT\",\n    \"SELECT\",\n    \"SLOT\",\n    \"SMALL\",\n    \"SPAN\",\n    \"STRONG\",\n    \"SUB\",\n    \"SUP\",\n    \"SVG\",\n    \"TEMPLATE\",\n    \"TEXTAREA\",\n    \"TIME\",\n    \"U\",\n    \"VAR\",\n    \"VIDEO\",\n    \"WBR\",\n    \"FONT\", // TODO @phoenix: font is deprecated, replace usage\n    // The following elements are phrasing content under specific conditions,\n    // evaluate if those conditions are applicable when using this set.\n    \"A\",\n    \"AREA\",\n    \"DEL\",\n    \"INS\",\n    \"LINK\",\n    \"MAP\",\n    \"META\",\n]);\n\nexport function isPhrasingContent(node) {\n    if (\n        node &&\n        (node.nodeType === Node.TEXT_NODE ||\n            (node.nodeType === Node.ELEMENT_NODE && phrasingTagNames.has(node.tagName)))\n    ) {\n        return true;\n    }\n    return false;\n}\n\nexport function containsAnyInline(element) {\n    if (!element) {\n        return false;\n    }\n    let child = element.firstChild;\n    while (child) {\n        if (\n            (!isBlock(child) && child.nodeType === Node.ELEMENT_NODE) ||\n            (child.nodeType === Node.TEXT_NODE && child.textContent.trim() !== \"\")\n        ) {\n            return true;\n        }\n        child = child.nextSibling;\n    }\n    return false;\n}\n\nexport function containsAnyNonPhrasingContent(element) {\n    if (!element) {\n        return false;\n    }\n    let child = element.firstChild;\n    while (child) {\n        if (!isPhrasingContent(child)) {\n            return true;\n        }\n        child = child.nextSibling;\n    }\n    return false;\n}\n\nexport function isEmbeddedComponent(node) {\n    return node.nodeType === Node.ELEMENT_NODE && node.matches(\"[data-embedded]\");\n}\n\n/**\n * A \"protected\" node will have its mutations filtered and not be registered\n * in an history step. Some editor features like selection handling, command\n * hint, toolbar, tooltip, etc. are also disabled. Protected roots have their\n * data-oe-protected attribute set to either \"\" or \"true\". If the closest parent\n * with a data-oe-protected attribute has the value \"false\", it is not\n * protected. Unknown values are ignored.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isProtected(node) {\n    if (!node) {\n        return false;\n    }\n    const candidate = node.parentElement\n        ? closestElement(node.parentElement, \"[data-oe-protected]\")\n        : null;\n    if (!candidate || candidate.dataset.oeProtected === \"false\") {\n        return false;\n    }\n    return true;\n}\n\n/**\n * A \"protecting\" element contains childNodes that are protected.\n *\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isProtecting(node) {\n    if (!node) {\n        return false;\n    }\n    return (\n        node.nodeType === Node.ELEMENT_NODE &&\n        node.dataset.oeProtected !== \"false\" &&\n        node.dataset.oeProtected !== undefined\n    );\n}\n\nexport function isUnprotecting(node) {\n    if (!node) {\n        return false;\n    }\n    return node.nodeType === Node.ELEMENT_NODE && node.dataset.oeProtected === \"false\";\n}\n\n// This is a list of \"paragraph-related elements\", defined as elements that\n// behave like paragraphs. It is non-exhaustive and should not be used as a\n// standalone. @see isParagraphRelatedElement\nexport const paragraphRelatedElements = [\"P\", \"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\", \"PRE\"];\n\n/**\n * Return true if the given node allows \"paragraph-related elements\".\n *\n * @see paragraphRelatedElements\n * @param {Node} node\n * @returns {boolean}\n */\nexport function allowsParagraphRelatedElements(node) {\n    return isBlock(node) && !isParagraphRelatedElement(node);\n}\n\nexport const phrasingContent = new Set([\"#text\", ...phrasingTagNames]);\nconst flowContent = new Set([...phrasingContent, ...paragraphRelatedElements, \"DIV\", \"HR\"]);\nexport const listItem = new Set([\"LI\"]);\nconst listContainers = new Set([\"UL\", \"OL\"]);\n\nconst allowedContent = {\n    BLOCKQUOTE: flowContent,\n    DIV: flowContent,\n    H1: phrasingContent,\n    H2: phrasingContent,\n    H3: phrasingContent,\n    H4: phrasingContent,\n    H5: phrasingContent,\n    H6: phrasingContent,\n    HR: new Set(),\n    LI: flowContent,\n    OL: listItem,\n    UL: listItem,\n    P: phrasingContent,\n    PRE: phrasingContent,\n    TD: flowContent,\n    TR: new Set([\"TD\"]),\n};\n\nexport function isParagraphRelatedElement(node) {\n    if (!node) {\n        return false;\n    }\n    return (\n        paragraphRelatedElements.includes(node.nodeName) ||\n        (node.nodeType === Node.ELEMENT_NODE && node.matches(baseContainerGlobalSelector))\n    );\n}\n\nexport const paragraphRelatedElementsSelector = [\n    ...paragraphRelatedElements,\n    baseContainerGlobalSelector,\n].join(\",\");\n\nexport function isListItemElement(node) {\n    return [...listItem].includes(node.nodeName);\n}\n\nexport const listItemElementSelector = [...listItem].join(\",\");\n\nexport function isListElement(node) {\n    return [...listContainers].includes(node.nodeName);\n}\n\nexport const listElementSelector = [...listContainers].join(\",\");\n\nexport function isTableCell(node) {\n    return [\"TH\", \"TD\"].includes(node.nodeName);\n}\n\n/**\n * @param {Element} parentBlock\n * @param {Node[]} nodes\n * @returns {boolean}\n */\nexport function isAllowedContent(parentBlock, nodes) {\n    let allowedContentSet = allowedContent[parentBlock.nodeName];\n    if (!allowedContentSet) {\n        // Spec: a block not listed in allowedContent allows anything.\n        // See \"custom-block\" in tests.\n        return true;\n    }\n    if (parentBlock.matches(baseContainerGlobalSelector)) {\n        // A baseContainer DIV can only have phrasingContent, as a P would.\n        allowedContentSet = phrasingContent;\n    }\n    return nodes.every((node) => allowedContentSet.has(node.nodeName));\n}\n\n/**\n * Checks whether or not the given block has any visible content, except for\n * a placeholder BR.\n *\n * @param {HTMLElement} blockEl\n * @returns {boolean}\n */\nexport function isEmptyBlock(blockEl) {\n    if (!blockEl || blockEl.nodeType !== Node.ELEMENT_NODE) {\n        return false;\n    }\n    if (visibleCharRegex.test(blockEl.textContent)) {\n        return false;\n    }\n    if (blockEl.querySelectorAll(\"br\").length >= 2) {\n        return false;\n    }\n    if (isProtecting(blockEl) || isProtected(blockEl)) {\n        // Protecting nodes should never be considered empty for editor\n        // operations, as their content is a \"black box\". Their content should\n        // be managed by a specialized plugin.\n        return false;\n    }\n    const nodes = blockEl.querySelectorAll(\"*\");\n    for (const node of nodes) {\n        // There is no text and no double BR, the only thing that could make\n        // this visible is a \"visible empty\" node like an image.\n        if (\n            node.nodeName != \"BR\" &&\n            (isSelfClosingElement(node) ||\n                isMediaElement(node) ||\n                isProtecting(node) ||\n                isButton(node))\n        ) {\n            return false;\n        }\n    }\n    return isBlock(blockEl);\n}\n/**\n * Checks whether or not the given block element has something to make it have\n * a visible height (except for padding / border).\n *\n * @param {HTMLElement} blockEl\n * @returns {boolean}\n */\nexport function isShrunkBlock(blockEl) {\n    return isEmptyBlock(blockEl) && !blockEl.querySelector(\"br\") && !isSelfClosingElement(blockEl);\n}\n\nexport function isEditorTab(node) {\n    return node && node.nodeName === \"SPAN\" && node.classList.contains(\"oe-tabs\");\n}\n\nexport function getDeepestPosition(node, offset) {\n    let direction = DIRECTIONS.RIGHT;\n    let next = node;\n    while (next) {\n        if (isTangible(next) || (isZWS(next) && isContentEditable(next))) {\n            // Valid node: update position then try to go deeper.\n            if (next !== node) {\n                [node, offset] = [next, direction ? 0 : nodeSize(next)];\n            }\n            // First switch direction to left if offset is at the end.\n            const childrenNodes = childNodes(node);\n            direction = offset < childrenNodes.length;\n            next = childrenNodes[direction ? offset : offset - 1];\n        } else if (direction && next.nextSibling && closestBlock(node).contains(next.nextSibling)) {\n            // Invalid node: skip to next sibling (without crossing blocks).\n            next = next.nextSibling;\n        } else {\n            // Invalid node: skip to previous sibling (without crossing blocks).\n            direction = DIRECTIONS.LEFT;\n            next = closestBlock(node).contains(next.previousSibling) && next.previousSibling;\n        }\n        // Avoid too-deep ranges inside self-closing elements like [BR, 0].\n        next = !isSelfClosingElement(next) && next;\n    }\n    return [node, offset];\n}\n\nexport function previousLeaf(node, editable, skipInvisible = false) {\n    let ancestor = node;\n    while (ancestor && !ancestor.previousSibling && ancestor !== editable) {\n        ancestor = ancestor.parentElement;\n    }\n    if (ancestor && ancestor !== editable) {\n        if (skipInvisible && !isVisible(ancestor.previousSibling)) {\n            return previousLeaf(ancestor.previousSibling, editable, skipInvisible);\n        } else {\n            const last = lastLeaf(ancestor.previousSibling);\n            if (skipInvisible && !isVisible(last)) {\n                return previousLeaf(last, editable, skipInvisible);\n            } else {\n                return last;\n            }\n        }\n    }\n}\nexport function nextLeaf(node, editable, skipInvisible = false) {\n    let ancestor = node;\n    while (ancestor && !ancestor.nextSibling && ancestor !== editable) {\n        ancestor = ancestor.parentElement;\n    }\n    if (ancestor && ancestor !== editable) {\n        if (skipInvisible && ancestor.nextSibling && !isVisible(ancestor.nextSibling)) {\n            return nextLeaf(ancestor.nextSibling, editable, skipInvisible);\n        } else {\n            const first = firstLeaf(ancestor.nextSibling);\n            if (skipInvisible && !isVisible(first)) {\n                return nextLeaf(first, editable, skipInvisible);\n            } else {\n                return first;\n            }\n        }\n    }\n}\n\nfunction hasPseudoElementContent(node, pseudoSelector) {\n    const content = getComputedStyle(node, pseudoSelector).getPropertyValue(\"content\");\n    return content && content !== \"none\";\n}\n\nconst NOT_A_NUMBER = /[^\\d]/g;\n\nexport function areSimilarElements(node, node2) {\n    if (![node, node2].every((n) => n?.nodeType === Node.ELEMENT_NODE)) {\n        return false; // The nodes don't both exist or aren't both elements.\n    }\n    if (node.nodeName !== node2.nodeName) {\n        return false; // The nodes aren't the same type of element.\n    }\n    for (const name of new Set([...node.getAttributeNames(), ...node2.getAttributeNames()])) {\n        if (name === \"style\") {\n            if (!hasSameStyleAttributes(node, node2)) {\n                return false;\n            }\n        } else if (name === \"class\") {\n            if (!hasSameClasses(node, node2)) {\n                return false; // The nodes don't have the same classes.\n            }\n        } else if (node.getAttribute(name) !== node2.getAttribute(name)) {\n            return false; // The nodes don't have the same attributes.\n        }\n    }\n    if (\n        [node, node2].some(\n            (n) => hasPseudoElementContent(n, \":before\") || hasPseudoElementContent(n, \":after\")\n        )\n    ) {\n        return false; // The nodes have pseudo elements with content.\n    }\n    if (isBlock(node)) {\n        return false;\n    }\n    const nodeStyle = getComputedStyle(node);\n    const node2Style = getComputedStyle(node2);\n    return (\n        !+nodeStyle.padding.replace(NOT_A_NUMBER, \"\") &&\n        !+node2Style.padding.replace(NOT_A_NUMBER, \"\") &&\n        !+nodeStyle.margin.replace(NOT_A_NUMBER, \"\") &&\n        !+node2Style.margin.replace(NOT_A_NUMBER, \"\")\n    );\n}\n\nexport function hasSameStyleAttributes(node, node2) {\n    const getNodeStyles = (node) =>\n        (node.getAttribute(\"style\") || \"\")\n            .split(\";\")\n            .map((style) => style.trim())\n            .filter(Boolean);\n    const [nodeStyles, node2Styles] = [node, node2].map(getNodeStyles);\n    return (\n        nodeStyles.length === node2Styles.length &&\n        nodeStyles.every((style) => node2Styles.includes(style))\n    );\n}\n\nexport function hasSameClasses(node, node2) {\n    const getNodeClasses = (node) =>\n        (node.getAttribute(\"class\") || \"\")\n            .split(/\\s+/)\n            .map((c) => c.trim())\n            .filter(Boolean);\n    const [nodeClasses, node2Classes] = [node, node2].map(getNodeClasses);\n    return (\n        nodeClasses.length === node2Classes.length &&\n        nodeClasses.every((cls) => node2Classes.includes(cls))\n    );\n}\n\nexport function isTextNode(node) {\n    return node.nodeType === Node.TEXT_NODE;\n}\n\nexport function isElement(node) {\n    return node.nodeType === Node.ELEMENT_NODE;\n}\n\nexport function isContentEditable(node) {\n    const element = isTextNode(node) ? node.parentElement : node;\n    return element && element.isContentEditable;\n}\n\nexport function isContentEditableAncestor(node) {\n    if (node.nodeType !== Node.ELEMENT_NODE) {\n        return false;\n    }\n    return node.isContentEditable && node.matches(\"[contenteditable]\");\n}\n\n/**\n * Checks if all classes in node are present in node2 (subset check)\n */\nfunction hasClassesSubset(node, node2) {\n    const getNodeClasses = (n) => (n || \"\").trim().split(/\\s+/).filter(Boolean);\n    const [nodeClasses, node2Classes] = [node, node2].map(getNodeClasses);\n    return nodeClasses.every((cls) => node2Classes.includes(cls));\n}\n\n/**\n * Checks if all styles in node are present in node2 (subset check)\n */\nfunction hasStylesSubset(node, node2) {\n    const getNodeStyles = (n) =>\n        (n || \"\")\n            .split(\";\")\n            .map((s) => s.trim())\n            .filter(Boolean);\n    const [nodeStyles, node2Styles] = [node, node2].map(getNodeStyles);\n    return nodeStyles.every((style) => node2Styles.includes(style));\n}\n\n/**\n * Checks if a node is redundant based on its closest element with same tag.\n *\n * A node is considered redundant if:\n * - It is an Element node with a parent.\n * - There is a closest element with the same tag name.\n * - All of the node's attributes are present in that closest element:\n *   - All classes exist in the closest element's class list (subset check).\n *   - All inline styles are present in the closest element's style attribute (subset check).\n *   - All other attributes must have identical values.\n *\n * @param {Node} node - The DOM node to evaluate.\n * @returns {boolean} True if the node is redundant, false otherwise.\n */\nexport function isRedundantElement(node) {\n    // Check for valid element node and existence of a parent.\n    if (!node || node.nodeType !== Node.ELEMENT_NODE || !node.parentElement) {\n        return false;\n    }\n\n    // Find the closest element with the same tag name.\n    const closestEl = closestElement(node.parentElement, node.tagName);\n    if (!closestEl) {\n        return false;\n    }\n\n    // Check each attribute from node.\n    for (const { name: attrName, value: nodeAttrVal } of node.attributes) {\n        const closestElAttrVal = closestEl.getAttribute(attrName);\n\n        if (!closestElAttrVal) {\n            return false; // Attribute missing in closest element.\n        }\n\n        if (attrName === \"class\") {\n            // All classes on the node must exist in closest element.\n            if (!hasClassesSubset(nodeAttrVal, closestElAttrVal)) {\n                return false;\n            }\n        } else if (attrName === \"style\") {\n            // All inline styles on the node must exist in closest element.\n            if (!hasStylesSubset(nodeAttrVal, closestElAttrVal)) {\n                return false;\n            }\n        } else {\n            // For other attributes, values must match exactly.\n            if (nodeAttrVal !== closestElAttrVal) {\n                return false;\n            }\n        }\n    }\n\n    return true;\n}\n", "import { isBlock } from \"./blocks\";\nimport { CTGROUPS, CTYPES, ctypeToString } from \"./content_types\";\nimport { isInPre, isVisible, isWhitespace, whitespace } from \"./dom_info\";\nimport {\n    PATH_END_REASONS,\n    ancestors,\n    closestElement,\n    closestPath,\n    createDOMPathGenerator,\n} from \"./dom_traversal\";\nimport { DIRECTIONS, leftPos, rightPos } from \"./position\";\n\nconst prepareUpdateLockedEditables = new Set();\n/**\n * Any editor command is applied to a selection (collapsed or not). After the\n * command, the content type on the selection boundaries, in both direction,\n * should be preserved (some whitespace should disappear as went from collapsed\n * to non collapsed, or converted to &nbsp; as went from non collapsed to\n * collapsed, there also <br> to remove/duplicate, etc).\n *\n * This function returns a callback which allows to do that after the command\n * has been done.\n *\n * Note: the method has been made generic enough to work with non-collapsed\n * selection but can be used for an unique cursor position.\n *\n * @param {HTMLElement} el\n * @param {number} offset\n * @param {...(HTMLElement|number)} args - argument 1 and 2 can be repeated for\n *     multiple preparations with only one restore callback returned. Note: in\n *     that case, the positions should be given in the document node order.\n * @param {Object} [options]\n * @param {boolean} [options.allowReenter = true] - if false, all calls to\n *     prepareUpdate before this one gets restored will be ignored.\n * @param {string} [options.label = <random 6 character string>]\n * @param {boolean} [options.debug = false] - if true, adds nicely formatted\n *     console logs to help with debugging.\n * @returns {function}\n */\nexport function prepareUpdate(...args) {\n    const closestRoot =\n        args.length &&\n        ancestors(args[0]).find((ancestor) => ancestor.classList.contains(\"odoo-editor-editable\"));\n    const isPrepareUpdateLocked = closestRoot && prepareUpdateLockedEditables.has(closestRoot);\n    const hash = (Math.random() + 1).toString(36).substring(7);\n    const options = {\n        allowReenter: true,\n        label: hash,\n        debug: false,\n        ...(args.length && args[args.length - 1] instanceof Object ? args.pop() : {}),\n    };\n    if (options.debug) {\n        console.log(\n            \"%cPreparing%c update: \" +\n                options.label +\n                (options.label === hash ? \"\" : ` (${hash})`) +\n                \"%c\" +\n                (isPrepareUpdateLocked ? \" LOCKED\" : \"\"),\n            \"color: cyan;\",\n            \"color: white;\",\n            \"color: red; font-weight: bold;\"\n        );\n    }\n    if (isPrepareUpdateLocked) {\n        return () => {\n            if (options.debug) {\n                console.log(\n                    \"%cRestoring%c update: \" +\n                        options.label +\n                        (options.label === hash ? \"\" : ` (${hash})`) +\n                        \"%c LOCKED\",\n                    \"color: lightgreen;\",\n                    \"color: white;\",\n                    \"color: red; font-weight: bold;\"\n                );\n            }\n        };\n    }\n    if (!options.allowReenter && closestRoot) {\n        prepareUpdateLockedEditables.add(closestRoot);\n    }\n    const positions = [...args];\n\n    // Check the state in each direction starting from each position.\n    const restoreData = [];\n    let el, offset;\n    while (positions.length) {\n        // Note: important to get the positions in reverse order to restore\n        // right side before left side.\n        offset = positions.pop();\n        el = positions.pop();\n        const left = getState(el, offset, DIRECTIONS.LEFT);\n        const right = getState(el, offset, DIRECTIONS.RIGHT, left.cType);\n        if (options.debug) {\n            const editable = el && closestElement(el, \".odoo-editor-editable\");\n            const oldEditableHTML =\n                (editable && editable.innerHTML.replaceAll(\" \", \"_\").replaceAll(\"\\u200B\", \"ZWS\")) ||\n                \"\";\n            left.oldEditableHTML = oldEditableHTML;\n            right.oldEditableHTML = oldEditableHTML;\n        }\n        restoreData.push(left, right);\n    }\n\n    // Create the callback that will be able to restore the state in each\n    // direction wherever the node in the opposite direction has landed.\n    return function restoreStates() {\n        if (options.debug) {\n            console.log(\n                \"%cRestoring%c update: \" +\n                    options.label +\n                    (options.label === hash ? \"\" : ` (${hash})`),\n                \"color: lightgreen;\",\n                \"color: white;\"\n            );\n        }\n        for (const data of restoreData) {\n            restoreState(data, options.debug);\n        }\n        if (!options.allowReenter && closestRoot) {\n            prepareUpdateLockedEditables.delete(closestRoot);\n        }\n    };\n}\n\nexport const leftLeafOnlyNotBlockPath = createDOMPathGenerator(DIRECTIONS.LEFT, {\n    leafOnly: true,\n    stopTraverseFunction: isBlock,\n    stopFunction: isBlock,\n});\n\nconst rightLeafOnlyNotBlockPath = createDOMPathGenerator(DIRECTIONS.RIGHT, {\n    leafOnly: true,\n    stopTraverseFunction: isBlock,\n    stopFunction: isBlock,\n});\n\n/**\n * Retrieves the \"state\" from a given position looking at the given direction.\n * The \"state\" is the type of content. The functions also returns the first\n * meaninful node looking in the opposite direction = the first node we trust\n * will not disappear if a command is played in the given direction.\n *\n * Note: only work for in-between nodes positions. If the position is inside a\n * text node, first split it @see splitTextNode.\n *\n * @param {HTMLElement} el\n * @param {number} offset\n * @param {boolean} direction @see DIRECTIONS.LEFT @see DIRECTIONS.RIGHT\n * @param {CTYPES} [leftCType]\n * @returns {Object}\n */\nexport function getState(el, offset, direction, leftCType) {\n    const leftDOMPath = leftLeafOnlyNotBlockPath;\n    const rightDOMPath = rightLeafOnlyNotBlockPath;\n\n    let domPath;\n    let inverseDOMPath;\n    const whitespaceAtStartRegex = new RegExp(\"^\" + whitespace + \"+\");\n    const whitespaceAtEndRegex = new RegExp(whitespace + \"+$\");\n    const reasons = [];\n    if (direction === DIRECTIONS.LEFT) {\n        domPath = leftDOMPath(el, offset, reasons);\n        inverseDOMPath = rightDOMPath(el, offset);\n    } else {\n        domPath = rightDOMPath(el, offset, reasons);\n        inverseDOMPath = leftDOMPath(el, offset);\n    }\n\n    // TODO I think sometimes, the node we have to consider as the\n    // anchor point to restore the state is not the first one of the inverse\n    // path (like for example, empty text nodes that may disappear\n    // after the command so we would not want to get those ones).\n    const boundaryNode = inverseDOMPath.next().value;\n\n    // We only traverse through deep inline nodes. If we cannot find a\n    // meanfingful state between them, that means we hit a block.\n    let cType = undefined;\n\n    // Traverse the DOM in the given direction to check what type of content\n    // there is.\n    let lastSpace = null;\n    for (const node of domPath) {\n        if (node.nodeType === Node.TEXT_NODE) {\n            const value = node.nodeValue;\n            // If we hit a text node, the state depends on the path direction:\n            // any space encountered backwards is a visible space if we hit\n            // visible content afterwards. If going forward, spaces are only\n            // visible if we have content backwards.\n            if (direction === DIRECTIONS.LEFT) {\n                if (!isWhitespace(value)) {\n                    if (lastSpace) {\n                        cType = CTYPES.SPACE;\n                    } else {\n                        const rightLeaf = rightLeafOnlyNotBlockPath(node).next().value;\n                        const hasContentRight =\n                            rightLeaf && !whitespaceAtStartRegex.test(rightLeaf.textContent);\n                        cType =\n                            !hasContentRight && whitespaceAtEndRegex.test(node.textContent)\n                                ? CTYPES.SPACE\n                                : CTYPES.CONTENT;\n                    }\n                    break;\n                }\n                if (value.length) {\n                    lastSpace = node;\n                }\n            } else {\n                leftCType = leftCType || getState(el, offset, DIRECTIONS.LEFT).cType;\n                if (whitespaceAtStartRegex.test(value)) {\n                    const leftLeaf = leftLeafOnlyNotBlockPath(node).next().value;\n                    const hasContentLeft =\n                        leftLeaf && !whitespaceAtEndRegex.test(leftLeaf.textContent);\n                    const rct = !isWhitespace(value)\n                        ? CTYPES.CONTENT\n                        : getState(...rightPos(node), DIRECTIONS.RIGHT).cType;\n                    cType =\n                        leftCType & CTYPES.CONTENT &&\n                        rct & (CTYPES.CONTENT | CTYPES.BR) &&\n                        !hasContentLeft\n                            ? CTYPES.SPACE\n                            : rct;\n                    break;\n                }\n                if (!isWhitespace(value)) {\n                    cType = CTYPES.CONTENT;\n                    break;\n                }\n            }\n        } else if (node.nodeName === \"BR\") {\n            cType = CTYPES.BR;\n            break;\n        } else if (isVisible(node)) {\n            // E.g. an image\n            cType = CTYPES.CONTENT;\n            break;\n        }\n    }\n\n    if (cType === undefined) {\n        cType = reasons.includes(PATH_END_REASONS.BLOCK_HIT)\n            ? CTYPES.BLOCK_OUTSIDE\n            : CTYPES.BLOCK_INSIDE;\n    }\n\n    return {\n        node: boundaryNode,\n        direction: direction,\n        cType: cType, // Short for contentType\n    };\n}\nconst priorityRestoreStateRules = [\n    // Each entry is a list of two objects, with each key being optional (the\n    // more key-value pairs, the bigger the priority).\n    // {direction: ..., cType1: ..., cType2: ...}\n    // ->\n    // {spaceVisibility: (false|true), brVisibility: (false|true)}\n    [\n        // Replace a space by &nbsp; when it was not collapsed before and now is\n        // collapsed (one-letter word removal for example).\n        { cType1: CTYPES.CONTENT, cType2: CTYPES.SPACE | CTGROUPS.BLOCK },\n        { spaceVisibility: true },\n    ],\n    [\n        // Replace a space by &nbsp; when it was content before and now it is\n        // a BR.\n        { direction: DIRECTIONS.LEFT, cType1: CTGROUPS.INLINE, cType2: CTGROUPS.BR },\n        { spaceVisibility: true },\n    ],\n    [\n        // Replace a space by &nbsp; when it was content before and now it is\n        // a BR (removal of last character before a BR for example).\n        { direction: DIRECTIONS.RIGHT, cType1: CTGROUPS.CONTENT, cType2: CTGROUPS.BR },\n        { spaceVisibility: true },\n    ],\n    [\n        // Replace a space by &nbsp; when it was visible thanks to a BR which\n        // is now gone.\n        { direction: DIRECTIONS.RIGHT, cType1: CTGROUPS.BR, cType2: CTYPES.SPACE },\n        { spaceVisibility: true },\n    ],\n    [\n        // Replace a space by &nbsp; when it was visible thanks to a BR which\n        // is now gone and duplicate a BR which was visible thanks to a second\n        // BR which is now gone.\n        { direction: DIRECTIONS.RIGHT, cType1: CTGROUPS.BR, cType2: CTGROUPS.BLOCK },\n        { spaceVisibility: true, brVisibility: true },\n    ],\n    [\n        // Remove all collapsed spaces when a space is removed.\n        { cType1: CTYPES.SPACE },\n        { spaceVisibility: false },\n    ],\n    [\n        // Remove spaces once the preceeding BR is removed\n        { direction: DIRECTIONS.LEFT, cType1: CTGROUPS.BR },\n        { spaceVisibility: false },\n    ],\n    [\n        // Remove space before block once content is put after it (otherwise it\n        // would become visible).\n        { cType1: CTGROUPS.BLOCK, cType2: CTGROUPS.INLINE | CTGROUPS.BR },\n        { spaceVisibility: false },\n    ],\n    [\n        // Duplicate a BR once the content afterwards disappears\n        { direction: DIRECTIONS.RIGHT, cType1: CTGROUPS.INLINE, cType2: CTGROUPS.BLOCK },\n        { brVisibility: true },\n    ],\n    [\n        // Remove a BR at the end of a block once inline content is put after\n        // it (otherwise it would act as a line break).\n        {\n            direction: DIRECTIONS.RIGHT,\n            cType1: CTGROUPS.BLOCK,\n            cType2: CTGROUPS.INLINE | CTGROUPS.BR,\n        },\n        { brVisibility: false },\n    ],\n    [\n        // Remove a BR once the BR that preceeds it is now replaced by\n        // content (or if it was a BR at the start of a block which now is\n        // a trailing BR).\n        {\n            direction: DIRECTIONS.LEFT,\n            cType1: CTGROUPS.BR | CTGROUPS.BLOCK,\n            cType2: CTGROUPS.INLINE,\n        },\n        { brVisibility: false, extraBRRemovalCondition: (brNode) => isFakeLineBreak(brNode) },\n    ],\n];\nfunction restoreStateRuleHashCode(direction, cType1, cType2) {\n    return `${direction}-${cType1}-${cType2}`;\n}\nconst allRestoreStateRules = (function () {\n    const map = new Map();\n\n    const keys = [\"direction\", \"cType1\", \"cType2\"];\n    for (const direction of Object.values(DIRECTIONS)) {\n        for (const cType1 of Object.values(CTYPES)) {\n            for (const cType2 of Object.values(CTYPES)) {\n                const rule = { direction: direction, cType1: cType1, cType2: cType2 };\n\n                // Search for the rules which match whatever their priority\n                const matchedRules = [];\n                for (const entry of priorityRestoreStateRules) {\n                    let priority = 0;\n                    for (const key of keys) {\n                        const entryKeyValue = entry[0][key];\n                        if (entryKeyValue !== undefined) {\n                            if (\n                                typeof entryKeyValue === \"boolean\"\n                                    ? rule[key] === entryKeyValue\n                                    : rule[key] & entryKeyValue\n                            ) {\n                                priority++;\n                            } else {\n                                priority = -1;\n                                break;\n                            }\n                        }\n                    }\n                    if (priority >= 0) {\n                        matchedRules.push([priority, entry[1]]);\n                    }\n                }\n\n                // Create the final rule by merging found rules by order of\n                // priority\n                const finalRule = {};\n                for (let p = 0; p <= keys.length; p++) {\n                    for (const entry of matchedRules) {\n                        if (entry[0] === p) {\n                            Object.assign(finalRule, entry[1]);\n                        }\n                    }\n                }\n\n                // Create an unique identifier for the set of values\n                // direction - state 1 - state2 to add the rule in the map\n                const hashCode = restoreStateRuleHashCode(direction, cType1, cType2);\n                map.set(hashCode, finalRule);\n            }\n        }\n    }\n\n    return map;\n})();\n/**\n * Restores the given state starting before the given while looking in the given\n * direction.\n *\n * @param {Object} prevStateData @see getState\n * @param {boolean} debug=false - if true, adds nicely formatted\n *     console logs to help with debugging.\n * @returns {Object|undefined} the rule that was applied to restore the state,\n *     if any, for testing purposes.\n */\nexport function restoreState(prevStateData, debug = false) {\n    const { node, direction, cType: cType1, oldEditableHTML } = prevStateData;\n    if (!node || !node.parentNode) {\n        // FIXME sometimes we want to restore the state starting from a node\n        // which has been removed by another restoreState call... Not sure if\n        // it is a problem or not, to investigate.\n        return;\n    }\n    const [el, offset] = direction === DIRECTIONS.LEFT ? leftPos(node) : rightPos(node);\n    const { cType: cType2 } = getState(el, offset, direction);\n\n    /**\n     * Knowing the old state data and the new state data, we know if we have to\n     * do something or not, and what to do.\n     */\n    const ruleHashCode = restoreStateRuleHashCode(direction, cType1, cType2);\n    const rule = allRestoreStateRules.get(ruleHashCode);\n    if (debug) {\n        const editable = closestElement(node, \".odoo-editor-editable\");\n        console.log(\n            \"%c\" +\n                node.textContent.replaceAll(\" \", \"_\").replaceAll(\"\\u200B\", \"ZWS\") +\n                \"\\n\" +\n                \"%c\" +\n                (direction === DIRECTIONS.LEFT ? \"left\" : \"right\") +\n                \"\\n\" +\n                \"%c\" +\n                ctypeToString(cType1) +\n                \"\\n\" +\n                \"%c\" +\n                ctypeToString(cType2) +\n                \"\\n\" +\n                \"%c\" +\n                \"BEFORE: \" +\n                (oldEditableHTML || \"(unavailable)\") +\n                \"\\n\" +\n                \"%c\" +\n                \"AFTER:  \" +\n                (editable\n                    ? editable.innerHTML.replaceAll(\" \", \"_\").replaceAll(\"\\u200B\", \"ZWS\")\n                    : \"(unavailable)\") +\n                \"\\n\",\n            \"color: white; display: block; width: 100%;\",\n            \"color: \" +\n                (direction === DIRECTIONS.LEFT ? \"magenta\" : \"lightgreen\") +\n                \"; display: block; width: 100%;\",\n            \"color: pink; display: block; width: 100%;\",\n            \"color: lightblue; display: block; width: 100%;\",\n            \"color: white; display: block; width: 100%;\",\n            \"color: white; display: block; width: 100%;\",\n            rule\n        );\n    }\n    if (Object.values(rule).filter((x) => x !== undefined).length) {\n        const inverseDirection = direction === DIRECTIONS.LEFT ? DIRECTIONS.RIGHT : DIRECTIONS.LEFT;\n        enforceWhitespace(el, offset, inverseDirection, rule);\n    }\n    return rule;\n}\n\n/**\n * Returns whether or not the given node is a BR element which does not really\n * act as a line break, but as a placeholder for the cursor or to make some left\n * element (like a space) visible.\n * @todo @phoenix this depends on state, so hard to move it to dom_info\n *\n * @param {HTMLBRElement} brEl\n * @returns {boolean}\n */\nexport function isFakeLineBreak(brEl) {\n    return !(getState(...rightPos(brEl), DIRECTIONS.RIGHT).cType & (CTYPES.CONTENT | CTGROUPS.BR));\n}\n\n/**\n * Enforces the whitespace and BR visibility in the given direction starting\n * from the given position.\n *\n * @param {HTMLElement} el\n * @param {number} offset\n * @param {number} direction @see DIRECTIONS.LEFT @see DIRECTIONS.RIGHT\n * @param {Object} rule\n * @param {boolean} [rule.spaceVisibility]\n * @param {boolean} [rule.brVisibility]\n */\nexport function enforceWhitespace(el, offset, direction, rule) {\n    const document = el.ownerDocument;\n    let domPath, whitespaceAtEdgeRegex;\n    if (direction === DIRECTIONS.LEFT) {\n        domPath = leftLeafOnlyNotBlockPath(el, offset);\n        whitespaceAtEdgeRegex = new RegExp(whitespace + \"+$\");\n    } else {\n        domPath = rightLeafOnlyNotBlockPath(el, offset);\n        whitespaceAtEdgeRegex = new RegExp(\"^\" + whitespace + \"+\");\n    }\n\n    const invisibleSpaceTextNodes = [];\n    let foundVisibleSpaceTextNode = null;\n    for (const node of domPath) {\n        if (node.nodeName === \"BR\") {\n            if (rule.brVisibility === undefined) {\n                break;\n            }\n            if (rule.brVisibility) {\n                node.before(document.createElement(\"br\"));\n            } else {\n                if (!rule.extraBRRemovalCondition || rule.extraBRRemovalCondition(node)) {\n                    node.remove();\n                }\n            }\n            break;\n        } else if (node.nodeType === Node.TEXT_NODE && !isInPre(node)) {\n            if (whitespaceAtEdgeRegex.test(node.nodeValue)) {\n                // If we hit spaces going in the direction, either they are in a\n                // visible text node and we have to change the visibility of\n                // those spaces, or it is in an invisible text node. In that\n                // last case, we either remove the spaces if there are spaces in\n                // a visible text node going further in the direction or we\n                // change the visiblity or those spaces.\n                if (!isWhitespace(node)) {\n                    foundVisibleSpaceTextNode = node;\n                    break;\n                } else {\n                    invisibleSpaceTextNodes.push(node);\n                }\n            } else if (!isWhitespace(node)) {\n                break;\n            }\n        } else {\n            break;\n        }\n    }\n\n    if (rule.spaceVisibility === undefined) {\n        return;\n    }\n    if (!rule.spaceVisibility) {\n        for (const node of invisibleSpaceTextNodes) {\n            // Empty and not remove to not mess with offset-based positions in\n            // commands implementation, also remove non-block empty parents.\n            node.nodeValue = \"\";\n            const ancestorPath = closestPath(node.parentNode);\n            let toRemove = null;\n            for (const pNode of ancestorPath) {\n                if (toRemove) {\n                    toRemove.remove();\n                }\n                if (pNode.childNodes.length === 1 && !isBlock(pNode)) {\n                    pNode.after(node);\n                    toRemove = pNode;\n                } else {\n                    break;\n                }\n            }\n        }\n    }\n    const spaceNode = foundVisibleSpaceTextNode || invisibleSpaceTextNodes[0];\n    if (spaceNode) {\n        let spaceVisibility = rule.spaceVisibility;\n        // In case we are asked to replace the space by a &nbsp;, disobey and\n        // do the opposite if that space is currently not visible\n        // TODO I'd like this to not be needed, it feels wrong...\n        if (\n            spaceVisibility &&\n            !foundVisibleSpaceTextNode &&\n            getState(...rightPos(spaceNode), DIRECTIONS.RIGHT).cType & CTGROUPS.BLOCK &&\n            getState(...leftPos(spaceNode), DIRECTIONS.LEFT).cType !== CTYPES.CONTENT\n        ) {\n            spaceVisibility = false;\n        }\n        spaceNode.nodeValue = spaceNode.nodeValue.replace(\n            whitespaceAtEdgeRegex,\n            spaceVisibility ? \"\\u00A0\" : \"\"\n        );\n    }\n}\n\n/**\n * Call this function to start watching for mutations.\n * Call the returned function to stop watching and get the mutation records.\n *\n * @returns {() => MutationRecord[]}\n */\nexport function observeMutations(target, observerOptions) {\n    const records = [];\n    const observerCallback = (mutations) => records.push(...mutations);\n    const observer = new MutationObserver(observerCallback);\n    observer.observe(target, observerOptions);\n    return () => {\n        observerCallback(observer.takeRecords());\n        observer.disconnect();\n        return records;\n    };\n}\n", "import { DIRECTIONS } from \"./position\";\n\nexport const closestPath = function* (node) {\n    while (node) {\n        yield node;\n        node = node.parentNode;\n    }\n};\n\n/**\n * Find a node.\n * @param {findCallback} findCallback - This callback check if this function\n *      should return `node`.\n * @param {findCallback} stopCallback - This callback check if this function\n *      should stop when it receive `node`.\n */\nexport function findNode(domPath, findCallback = () => true, stopCallback = () => false) {\n    for (const node of domPath) {\n        if (findCallback(node)) {\n            return node;\n        }\n        if (stopCallback(node)) {\n            break;\n        }\n    }\n    return null;\n}\n\n/**\n * @param {Node} node\n * @param {HTMLElement} limitAncestor - non inclusive limit ancestor to search for\n * @param {Function} predicate\n * @returns {Node|null}\n */\nexport function findUpTo(node, limitAncestor, predicate) {\n    while (node !== limitAncestor) {\n        if (predicate(node)) {\n            return node;\n        }\n        node = node.parentElement;\n    }\n    return null;\n}\n\n/**\n * @param {Node} node\n * @param {HTMLElement} limitAncestor - non inclusive limit ancestor to search for\n * @param {Function} predicate\n * @returns {Node|undefined}\n */\nexport function findFurthest(node, limitAncestor, predicate) {\n    const nodes = [];\n    while (node !== limitAncestor) {\n        nodes.push(node);\n        node = node.parentNode;\n    }\n    return nodes.findLast(predicate);\n}\n\n/**\n * Returns the closest HTMLElement of the provided Node. If the predicate is a\n * string, returns the closest HTMLElement that match the predicate selector. If\n * the predicate is a function, returns the closest element that matches the\n * predicate. Any returned element will be contained within the editable, or is\n * disconnected from any Document.\n *\n * Rationale: this helper is used to manipulate editor nodes, and should never\n * match any node outside of that scope. Disconnected nodes are assumed to be\n * from the editor, since they are likely removed nodes evaluated in the context\n * of the MutationObserver handler @see ProtectedNodePlugin\n *\n * @param {Node} node\n * @param {string | Function} [predicate='*']\n * @returns {HTMLElement|null}\n */\nexport function closestElement(node, predicate = \"*\") {\n    let element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n    const editable = element?.closest(\".odoo-editor-editable\");\n    if (typeof predicate === \"function\") {\n        while (element && !predicate(element)) {\n            element = element.parentElement;\n        }\n    } else {\n        element = element?.closest(predicate);\n    }\n    if ((editable && editable.contains(element)) || !node.isConnected) {\n        return element;\n    }\n    return null;\n}\n\n/**\n * Returns a list of all the ancestors nodes of the provided node.\n *\n * @param {Node} node\n * @param {Node} [editable] include to prevent bubbling up further than the editable.\n * @returns {HTMLElement[]}\n */\nexport function ancestors(node, editable) {\n    const result = [];\n    while (node && node.parentElement && node !== editable) {\n        result.push(node.parentElement);\n        node = node.parentElement;\n    }\n    return result;\n}\n\n/**\n * Get a static array of children, to avoid manipulating the live HTMLCollection\n * for better performances.\n *\n * @param {Element}} elem\n * @returns {Array<Element>} children\n */\nexport function children(elem) {\n    const children = [];\n    let child = elem.firstElementChild;\n    while (child) {\n        children.push(child);\n        child = child.nextElementSibling;\n    }\n    return children;\n}\n\n/**\n * Get a static array of childNodes, to avoid manipulating the live NodeList for\n * better performances.\n *\n * @param {Node}} node\n * @returns {Array<Node>} childNodes\n */\nexport function childNodes(node) {\n    const childNodes = [];\n    let child = node.firstChild;\n    while (child) {\n        childNodes.push(child);\n        child = child.nextSibling;\n    }\n    return childNodes;\n}\n\n/**\n * Take a node, return all of its descendants, in depth-first order.\n *\n * @param {Node} node\n * @returns {Node[]}\n */\nexport function descendants(node, posterity = []) {\n    let child = node.firstChild;\n    while (child) {\n        posterity.push(child);\n        descendants(child, posterity);\n        child = child.nextSibling;\n    }\n    return posterity;\n}\n\n/**\n * Values which can be returned while browsing the DOM which gives information\n * to why the path ended.\n */\nexport const PATH_END_REASONS = {\n    NO_NODE: 0,\n    BLOCK_OUT: 1,\n    BLOCK_HIT: 2,\n    OUT_OF_SCOPE: 3,\n};\n\n/**\n * Creates a generator function according to the given parameters. Pre-made\n * generators to traverse the DOM are made using this function:\n *\n * @see leftLeafFirstPath\n * @see leftLeafOnlyNotBlockPath\n * @see leftLeafOnlyInScopeNotBlockEditablePath\n * @see rightLeafOnlyNotBlockPath\n * @see rightLeafOnlyNotBlockNotEditablePath\n *\n * @param {boolean} direction\n * @param {Object} options\n * @param {boolean} [options.leafOnly] if true, do not yield any non-leaf node\n * @param {boolean} [options.inScope] if true, stop the generator as soon as a node is not\n *                      a descendant of `node` provided when traversing the\n *                      generated function.\n * @param {Function} [options.stopTraverseFunction] a function that takes a node\n *                      and should return true when a node descendant should not\n *                      be traversed.\n * @param {Function} [options.stopFunction] function that makes the generator stop when a\n *                      node is encountered.\n */\nexport function createDOMPathGenerator(\n    direction,\n    { leafOnly = false, inScope = false, stopTraverseFunction, stopFunction } = {}\n) {\n    const nextDeepest =\n        direction === DIRECTIONS.LEFT\n            ? (node) => lastLeaf(node.previousSibling, stopTraverseFunction)\n            : (node) => firstLeaf(node.nextSibling, stopTraverseFunction);\n\n    const firstNode =\n        direction === DIRECTIONS.LEFT\n            ? (node, offset) => lastLeaf(node.childNodes[offset - 1], stopTraverseFunction)\n            : (node, offset) => firstLeaf(node.childNodes[offset], stopTraverseFunction);\n\n    // Note \"reasons\" is a way for the caller to be able to know why the\n    // generator ended yielding values.\n    return function* (node, offset, reasons = []) {\n        let movedUp = false;\n\n        let currentNode = firstNode(node, offset);\n        if (!currentNode) {\n            movedUp = true;\n            currentNode = node;\n        }\n\n        while (currentNode) {\n            if (stopFunction && stopFunction(currentNode)) {\n                reasons.push(movedUp ? PATH_END_REASONS.BLOCK_OUT : PATH_END_REASONS.BLOCK_HIT);\n                break;\n            }\n            if (inScope && currentNode === node) {\n                reasons.push(PATH_END_REASONS.OUT_OF_SCOPE);\n                break;\n            }\n            if (!(leafOnly && movedUp)) {\n                yield currentNode;\n            }\n\n            movedUp = false;\n            let nextNode = nextDeepest(currentNode);\n            if (!nextNode) {\n                movedUp = true;\n                nextNode = currentNode.parentNode;\n            }\n            currentNode = nextNode;\n        }\n\n        reasons.push(PATH_END_REASONS.NO_NODE);\n    };\n}\n\n/**\n * Returns the deepest child in last position.\n *\n * @param {Node} node\n * @param {Function} [stopTraverseFunction]\n * @returns {Node}\n */\nexport function lastLeaf(node, stopTraverseFunction) {\n    while (node && node.lastChild && !(stopTraverseFunction && stopTraverseFunction(node))) {\n        node = node.lastChild;\n    }\n    return node;\n}\n/**\n * Returns the deepest child in first position.\n *\n * @param {Node} node\n * @param {Function} [stopTraverseFunction]\n * @returns {Node}\n */\nexport function firstLeaf(node, stopTraverseFunction) {\n    while (node && node.firstChild && !(stopTraverseFunction && stopTraverseFunction(node))) {\n        node = node.firstChild;\n    }\n    return node;\n}\n\n/**\n * Returns all the previous siblings of the given node until the first\n * sibling that does not satisfy the predicate, in lookup order.\n *\n * @param {Node} node\n * @param {Function} [predicate] (node: Node) => boolean\n */\nexport function getAdjacentPreviousSiblings(node, predicate = (n) => !!n) {\n    let previous = node.previousSibling;\n    const list = [];\n    while (previous && predicate(previous)) {\n        list.push(previous);\n        previous = previous.previousSibling;\n    }\n    return list;\n}\n/**\n * Returns all the next siblings of the given node until the first\n * sibling that does not satisfy the predicate, in lookup order.\n *\n * @param {Node} node\n * @param {Function} [predicate] (node: Node) => boolean\n */\nexport function getAdjacentNextSiblings(node, predicate = (n) => !!n) {\n    let next = node.nextSibling;\n    const list = [];\n    while (next && predicate(next)) {\n        list.push(next);\n        next = next.nextSibling;\n    }\n    return list;\n}\n/**\n * Returns all the adjacent siblings of the given node until the first sibling\n * (in both directions) that does not satisfy the predicate, in index order. If\n * the given node does not satisfy the predicate, an empty array is returned.\n *\n * @param {Node} node\n * @param {Function} [predicate] (node: Node) => boolean\n */\nexport function getAdjacents(node, predicate = (n) => !!n) {\n    const previous = getAdjacentPreviousSiblings(node, predicate);\n    const next = getAdjacentNextSiblings(node, predicate);\n    return predicate(node) ? [...previous.reverse(), node, ...next] : [];\n}\n\n/**\n * Returns the deepest common ancestor element of the given nodes within the\n * specified root element. If no root element is provided, the entire document\n * is considered as the root.\n *\n * @param {Node[]} nodes - The nodes for which to find the common ancestor.\n * @param {Element} [root] - The root element within which to search for the common ancestor.\n * @returns {Element|null} - The common ancestor element, or null if no common ancestor is found.\n */\nexport function getCommonAncestor(nodes, root = undefined) {\n    const pathsToRoot = nodes.map((node) => [node, ...ancestors(node, root)]);\n\n    let candidate = pathsToRoot[0]?.at(-1);\n    if (root && candidate !== root) {\n        return null;\n    }\n    let commonAncestor = null;\n    while (candidate && pathsToRoot.every((path) => path.at(-1) === candidate)) {\n        commonAncestor = candidate;\n        pathsToRoot.forEach((path) => path.pop());\n        candidate = pathsToRoot[0].at(-1);\n    }\n    return commonAncestor;\n}\n\n/**\n * Basically a wrapper around `root.querySelectorAll` that includes the\n * root.\n *\n * @param {Element} root\n * @param {string} selector\n * @returns {Generator<Element>}\n */\nexport const selectElements = function* (root, selector) {\n    if (root.matches(selector)) {\n        yield root;\n    }\n    for (const elem of root.querySelectorAll(selector)) {\n        yield elem;\n    }\n};\n", "import { makeDraggableHook } from \"@web/core/utils/draggable_hook_builder\";\nimport { pick } from \"@web/core/utils/objects\";\nimport { reactive } from \"@odoo/owl\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { closest, touching } from \"@web/core/utils/ui\";\n\n/** @typedef {import(\"@web/core/utils/draggable_hook_builder\").DraggableHandlerParams} DraggableHandlerParams */\n/** @typedef {import(\"@web/core/utils/draggable_hook_builder\").DraggableBuilderParams} DraggableBuilderParams */\n/** @typedef {import(\"@web/core/utils/draggable\").DraggableParams} DraggableParams */\n\n/** @typedef {DraggableHandlerParams & { dropzone: HTMLElement | null, helper: HTMLElement }} DragAndDropHandlerParams */\n/** @typedef {DraggableHandlerParams & { helper: HTMLElement }} DragAndDropStartParams */\n/** @typedef {DraggableHandlerParams & { dropzone: HTMLElement }} DropzoneHandlerParams */\n/**\n * @typedef DragAndDropParams\n * @extends {DraggableParams}\n *\n * MANDATORY\n * @property {(() => Array)} dropzones a function that returns the available dropzones\n * @property {(() => HTMLElement)} helper a function that returns a helper element\n * that will follow the cursor when dragging\n * @property {(() => HTMLElement)} scrollingElement a function that returns the\n * element on which a scroll should be triggered\n *\n * HANDLERS (Optional)\n * @property {(params: DragAndDropStartParams) => any} [onDragStart]\n * called when a dragging sequence is initiated\n * @property {(params: DropzoneHandlerParams) => any} [dropzoneOver]\n * called when an element is over a dropzone\n * @property {(params: DropzoneHandlerParams) => any} [dropzoneOut]\n * called when an element is leaving a dropzone\n * @property {(params: DragAndDropHandlerParams) => any} [onDrag]\n * called when an element is being dragged\n * @property {(params: DragAndDropHandlerParams) => any} [onDragEnd]\n * called when the dragging sequence is over\n */\n/**\n * @typedef NativeDraggableState\n * @property {(params: DraggableParams) => any} update\n * method to update the params of the draggable\n * @property {import(\"@web/core/utils/draggable\").DraggableState} state\n * state of the draggable component\n * @property {() => any} destroy\n * method to destroy and unbind the draggable component\n */\n/**\n * Utility function to create a native draggable component\n *\n * @param {DraggableBuilderParams} hookParams\n * @param {DraggableParams} initialParams\n * @returns {NativeDraggableState}\n */\nexport function useNativeDraggable(hookParams, initialParams) {\n    const setupFunctions = new Map();\n    const cleanupFunctions = [];\n    const currentParams = { ...initialParams };\n    const setupHooks = {\n        wrapState: reactive,\n        throttle: throttleForAnimation,\n        addListener: (el, type, callback, options) => {\n            el.addEventListener(type, callback, options);\n            cleanupFunctions.push(() => el.removeEventListener(type, callback));\n        },\n        setup: (setupFn, depsFn) => setupFunctions.set(setupFn, depsFn),\n        teardown: (cleanupFn) => {\n            cleanupFunctions.push(cleanupFn);\n        },\n    };\n    // Compatibility for tests\n    const el = initialParams.ref.el;\n    // TODO this is probably to be removed in master: the received params\n    // contain the selector that should be checked and it will be transferred\n    // to the makeDraggableHook function. There should not be any need to add\n    // the default selector class here.\n    el.classList.add(\"o_draggable\");\n    cleanupFunctions.push(() => el.classList.remove(\"o_draggable\"));\n\n    const draggableState = makeDraggableHook({ setupHooks, ...hookParams })(currentParams);\n    draggableState.enable = true;\n    const draggableComponent = {\n        state: draggableState,\n        update: (newParams) => {\n            Object.assign(currentParams, newParams);\n            setupFunctions.forEach((depsFn, setupFn) => setupFn(...depsFn()));\n        },\n        destroy: () => {\n            cleanupFunctions.forEach((cleanupFn) => cleanupFn());\n        },\n    };\n    draggableComponent.update({});\n    return draggableComponent;\n}\n\nfunction updateElementPosition(el, { x, y }, styleFn, offset = { x: 0, y: 0 }) {\n    return styleFn(el, { top: `${y - offset.y}px`, left: `${x - offset.x}px` });\n}\n/** @type DraggableBuilderParams */\nconst dragAndDropHookParams = {\n    name: \"useDragAndDrop\",\n    acceptedParams: {\n        dropzones: [Function],\n        scrollingElement: [Function],\n        helper: [Function],\n        extraWindow: [Object, Function],\n    },\n    edgeScrolling: { enabled: true },\n    onComputeParams({ ctx, params }) {\n        // The helper is mandatory and will follow the cursor instead\n        ctx.followCursor = false;\n        ctx.getScrollingElement = params.scrollingElement;\n        ctx.getHelper = params.helper;\n        ctx.getDropZones = params.dropzones;\n    },\n    onWillStartDrag: ({ ctx }) => {\n        ctx.current.container = ctx.getScrollingElement();\n        ctx.current.helperOffset = { x: 0, y: 0 };\n    },\n    onDragStart: ({ ctx, addStyle, addCleanup }) => {\n        // Use the helper as the tracking element to properly update scroll values.\n        ctx.current.helper = ctx.getHelper({ ...ctx.current, ...ctx.pointer });\n        ctx.current.helper.style.position = \"fixed\";\n        // We want the pointer events on the helper so that the cursor\n        // is properly displayed.\n        ctx.current.element.classList.remove(\"o_dragged\");\n        ctx.current.helper.style.cursor = ctx.cursor;\n        ctx.current.helper.style.pointerEvents = \"auto\";\n\n        // If the helper is inside the iframe, we want pointer events on the\n        // frame element so that they reach the window and properly apply\n        // the cursor.\n        const frameElement = ctx.current.helper.ownerDocument.defaultView.frameElement;\n        if (frameElement) {\n            addStyle(frameElement, { pointerEvents: \"auto\" });\n        }\n\n        addCleanup(() => ctx.current.helper.remove());\n\n        updateElementPosition(ctx.current.helper, ctx.pointer, addStyle, ctx.current.helperOffset);\n\n        return pick(ctx.current, \"element\", \"helper\");\n    },\n    onDrag: ({ ctx, addStyle, callHandler }) => {\n        ctx.current.helper.classList.add(\"o_draggable_dragging\");\n\n        updateElementPosition(ctx.current.helper, ctx.pointer, addStyle, ctx.current.helperOffset);\n        // Unfortunately, DOMRect is not an Object, so spreading operator from\n        // `touching` does not work, so convert DOMRect to plain object.\n        let helperRect = ctx.current.helper.getBoundingClientRect();\n        helperRect = {\n            x: helperRect.x,\n            y: helperRect.y,\n            width: helperRect.width,\n            height: helperRect.height,\n        };\n        const dropzoneEl = closest(touching(ctx.getDropZones(), helperRect), helperRect);\n        // Update the drop zone if it's in grid mode\n        if (\n            ctx.current.dropzone?.el &&\n            ctx.current.dropzone.el.classList.contains(\"oe_grid_zone\")\n        ) {\n            ctx.current.dropzone.rect = ctx.current.dropzone.el.getBoundingClientRect();\n        }\n        if (\n            ctx.current.dropzone &&\n            (ctx.current.dropzone.el === dropzoneEl ||\n                (!dropzoneEl &&\n                    touching([ctx.current.helper], ctx.current.dropzone.rect).length > 0))\n        ) {\n            // If no new dropzone but old one is still valid, return early.\n            return pick(ctx.current, \"element\", \"dropzone\", \"helper\");\n        }\n\n        if (ctx.current.dropzone && dropzoneEl !== ctx.current.dropzone.el) {\n            callHandler(\"dropzoneOut\", {\n                dropzone: ctx.current.dropzone,\n                helper: ctx.current.helper,\n            });\n            delete ctx.current.dropzone;\n        }\n\n        if (dropzoneEl) {\n            // Save rect information prior to calling the over function\n            // to keep a consistent dropzone even if content was added.\n            const rect = DOMRect.fromRect(dropzoneEl.getBoundingClientRect());\n            ctx.current.dropzone = {\n                el: dropzoneEl,\n                rect: {\n                    x: rect.x,\n                    y: rect.y,\n                    width: rect.width,\n                    height: rect.height,\n                },\n            };\n            callHandler(\"dropzoneOver\", {\n                dropzone: ctx.current.dropzone,\n                helper: ctx.current.helper,\n            });\n        }\n        return pick(ctx.current, \"element\", \"dropzone\", \"helper\");\n    },\n    onDragEnd({ ctx }) {\n        return pick(ctx.current, \"element\", \"dropzone\", \"helper\");\n    },\n};\n/**\n * Function to start a drag and drop handler\n *\n * @param {DragAndDropParams} initialParams params given to the drag and drop\n * component\n * @returns {NativeDraggableState}\n */\nexport function useDragAndDrop(initialParams) {\n    return useNativeDraggable(dragAndDropHookParams, initialParams);\n}\n", "export const fonts = {\n    /**\n     * Retrieves all the CSS rules which match the given parser (Regex).\n     *\n     * @param {Regex} filter\n     * @returns {Object[]} Array of CSS rules descriptions (objects). A rule is\n     *          defined by 3 values: 'selector', 'css' and 'names'. 'selector'\n     *          is a string which contains the whole selector, 'css' is a string\n     *          which contains the css properties and 'names' is an array of the\n     *          first captured groups for each selector part. E.g.: if the\n     *          filter is set to match .fa-* rules and capture the icon names,\n     *          the rule:\n     *              '.fa-alias1::before, .fa-alias2::before { hello: world; }'\n     *          will be retrieved as\n     *              {\n     *                  selector: '.fa-alias1::before, .fa-alias2::before',\n     *                  css: 'hello: world;',\n     *                  names: ['.fa-alias1', '.fa-alias2'],\n     *              }\n     */\n    cacheCssSelectors: {},\n    getCssSelectors: function (filter) {\n        if (this.cacheCssSelectors[filter]) {\n            return this.cacheCssSelectors[filter];\n        }\n        this.cacheCssSelectors[filter] = [];\n        var sheets = document.styleSheets;\n        for (var i = 0; i < sheets.length; i++) {\n            var rules;\n            try {\n                // try...catch because Firefox not able to enumerate\n                // document.styleSheets[].cssRules[] for cross-domain\n                // stylesheets.\n                rules = sheets[i].rules || sheets[i].cssRules;\n            } catch {\n                continue;\n            }\n            if (!rules) {\n                continue;\n            }\n\n            for (var r = 0; r < rules.length; r++) {\n                var selectorText = rules[r].selectorText;\n                if (!selectorText) {\n                    continue;\n                }\n                var selectors = selectorText.split(/\\s*,\\s*/);\n                var data = null;\n                for (var s = 0; s < selectors.length; s++) {\n                    var match = selectors[s].trim().match(filter);\n                    if (!match) {\n                        continue;\n                    }\n                    if (!data) {\n                        data = {\n                            selector: match[0],\n                            css: rules[r].cssText.replace(/(^.*\\{\\s*)|(\\s*\\}\\s*$)/g, \"\"),\n                            names: [match[1]],\n                        };\n                    } else {\n                        data.selector += \", \" + match[0];\n                        data.names.push(match[1]);\n                    }\n                }\n                if (data) {\n                    this.cacheCssSelectors[filter].push(data);\n                }\n            }\n        }\n        return this.cacheCssSelectors[filter];\n    },\n    /**\n     * List of font icons to load by editor. The icons are displayed in the media\n     * editor and identified like font and image (can be colored, spinned, resized\n     * with fa classes).\n     * To add font, push a new object {base, parser}\n     *\n     * - base: class who appear on all fonts\n     * - parser: regular expression used to select all font in css stylesheets\n     *\n     * @type Array\n     */\n    fontIcons: [{ base: \"fa\", parser: /\\.(fa-(?:\\w|-)+)::?before/i }],\n    computedFonts: false,\n    /**\n     * Searches the fonts described by the @see fontIcons variable.\n     */\n    computeFonts: function () {\n        if (!this.computedFonts) {\n            var self = this;\n            this.fontIcons.forEach((data) => {\n                data.cssData = self.getCssSelectors(data.parser);\n                data.alias = data.cssData.map((x) => x.names).flat();\n            });\n            this.computedFonts = true;\n        }\n    },\n};\n", "import { normalizeCSSColor } from \"@web/core/utils/colors\";\nimport { removeClass } from \"./dom\";\nimport { isBold, isDirectionSwitched, isItalic, isStrikeThrough, isUnderline } from \"./dom_info\";\nimport { closestElement, closestPath, findNode } from \"./dom_traversal\";\nimport { closestBlock, isBlock } from \"./blocks\";\n\n/**\n * Array of all the classes used by the editor to change the font size.\n */\nexport const FONT_SIZE_CLASSES = [\n    \"display-1-fs\",\n    \"display-2-fs\",\n    \"display-3-fs\",\n    \"display-4-fs\",\n    \"h1-fs\",\n    \"h2-fs\",\n    \"h3-fs\",\n    \"h4-fs\",\n    \"h5-fs\",\n    \"h6-fs\",\n    \"base-fs\",\n    \"small\",\n    \"o_small-fs\",\n];\n\nexport const TEXT_STYLE_CLASSES = [\"display-1\", \"display-2\", \"display-3\", \"display-4\", \"lead\"];\n\nexport const DEFAULT_FONT_SIZE_CLASSES = [\n    \"h1\",\n    \"h2\",\n    \"h3\",\n    \"h4\",\n    \"h5\",\n    \"h6\",\n    \"o_default_font_size\",\n];\n\nexport const FORMATTABLE_TAGS = [\"SPAN\", \"FONT\", \"B\", \"STRONG\", \"I\", \"EM\", \"U\", \"S\"];\n\nexport const formatsSpecs = {\n    italic: {\n        tagName: \"em\",\n        isFormatted: isItalic,\n        isTag: (node) => [\"EM\", \"I\"].includes(node.tagName),\n        hasStyle: (node) => Boolean(node.style && node.style[\"font-style\"]),\n        addStyle: (node) => (node.style[\"font-style\"] = \"italic\"),\n        addNeutralStyle: (node) => (node.style[\"font-style\"] = \"normal\"),\n        removeStyle: (node) => removeStyle(node, \"font-style\"),\n    },\n    bold: {\n        tagName: \"strong\",\n        isFormatted: isBold,\n        isTag: (node) => [\"STRONG\", \"B\"].includes(node.tagName),\n        hasStyle: (node) => Boolean(node.style && node.style[\"font-weight\"]),\n        addStyle: (node) => (node.style[\"font-weight\"] = \"bolder\"),\n        addNeutralStyle: (node) => {\n            node.style[\"font-weight\"] = \"normal\";\n        },\n        removeStyle: (node) => removeStyle(node, \"font-weight\"),\n    },\n    underline: {\n        tagName: \"u\",\n        isFormatted: isUnderline,\n        isTag: (node) => node.tagName === \"U\",\n        hasStyle: (node) =>\n            node.style &&\n            (node.style[\"text-decoration\"].includes(\"underline\") ||\n                node.style[\"text-decoration-line\"].includes(\"underline\")),\n        addStyle: (node) => (node.style[\"text-decoration-line\"] += \" underline\"),\n        removeStyle: (node) =>\n            removeStyle(\n                node,\n                node.style[\"text-decoration\"].includes(\"underline\")\n                    ? \"text-decoration\"\n                    : \"text-decoration-line\",\n                \"underline\"\n            ),\n    },\n    strikeThrough: {\n        tagName: \"s\",\n        isFormatted: isStrikeThrough,\n        isTag: (node) => node.tagName === \"S\",\n        hasStyle: (node) =>\n            node.style &&\n            (node.style[\"text-decoration\"].includes(\"line-through\") ||\n                node.style[\"text-decoration-line\"].includes(\"line-through\")),\n        addStyle: (node) => (node.style[\"text-decoration-line\"] += \" line-through\"),\n        removeStyle: (node) =>\n            removeStyle(\n                node,\n                node.style[\"text-decoration\"].includes(\"line-through\")\n                    ? \"text-decoration\"\n                    : \"text-decoration-line\",\n                \"line-through\"\n            ),\n    },\n    fontFamily: {\n        isFormatted: (node) => !!closestElement(node, (el) => el.style[\"font-family\"]),\n        hasStyle: (node) => node.style && node.style[\"font-family\"],\n        addStyle: (node, props) => {\n            removeStyle(node, \"font-family\");\n            if (props.fontFamily) {\n                node.style[\"font-family\"] = props.fontFamily;\n            }\n        },\n        removeStyle: (node) => removeStyle(node, \"font-family\"),\n    },\n    fontSize: {\n        isFormatted: (node, props) => {\n            const fontSize = (\n                findNode(closestPath(node), (el) => el.style?.[\"font-size\"], isBlock) ||\n                closestElement(node, \"li\")\n            )?.style[\"font-size\"];\n            return props?.size ? fontSize === props.size : fontSize;\n        },\n        hasStyle: (node) => node.style && node.style[\"font-size\"],\n        addStyle: (node, props) => {\n            node.style[\"font-size\"] = props.size;\n            removeClass(node, ...FONT_SIZE_CLASSES);\n        },\n        removeStyle: (node) => removeStyle(node, \"font-size\"),\n    },\n    setFontSizeClassName: {\n        isFormatted: (node, props) =>\n            props?.className\n                ? FONT_SIZE_CLASSES.includes(props.className) &&\n                  !!(\n                      findNode(\n                          closestPath(node),\n                          (el) => el.classList?.contains(props.className),\n                          (el) => el === closestBlock(node).parentElement\n                      ) || closestElement(node, \"li\")?.classList?.contains(props.className)\n                  )\n                : !!findNode(\n                      closestPath(node),\n                      (el) => FONT_SIZE_CLASSES.find((cls) => el.classList?.contains(cls)),\n                      (el) => el === closestBlock(node).parentElement\n                  ) ||\n                  FONT_SIZE_CLASSES.find((cls) =>\n                      closestElement(node, \"li\")?.classList.contains(cls)\n                  ),\n        hasStyle: (node, props) =>\n            [...FONT_SIZE_CLASSES, ...TEXT_STYLE_CLASSES, ...DEFAULT_FONT_SIZE_CLASSES].find(\n                (cls) => node.classList.contains(cls)\n            ),\n        addStyle: (node, props) => {\n            node.style.removeProperty(\"font-size\");\n            node.classList.add(props.className);\n        },\n        removeStyle: (node) => {\n            removeStyle(node, \"font-size\");\n            removeClass(node, ...FONT_SIZE_CLASSES);\n            // Typography classes should be preserved on block elements since\n            // they act as semantic equivalents of <h1>, <h2>, etc., not just\n            // removable styles.\n            if (!isBlock(node)) {\n                removeClass(node, ...TEXT_STYLE_CLASSES, ...DEFAULT_FONT_SIZE_CLASSES);\n            }\n        },\n        addNeutralStyle: function (node) {\n            const block = closestBlock(node);\n            if ([\"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\"].includes(block.nodeName)) {\n                node.classList.add(block.nodeName.toLowerCase());\n            } else {\n                node.classList.add(\"o_default_font_size\");\n            }\n        },\n    },\n    switchDirection: {\n        isFormatted: (node, props) => isDirectionSwitched(node, props.editable),\n    },\n};\n\nfunction removeStyle(node, styleName, item) {\n    if (item) {\n        const newStyle = node.style[styleName]\n            .split(\" \")\n            .filter((x) => x !== item)\n            .join(\" \");\n        node.style[styleName] = newStyle || null;\n    } else {\n        node.style[styleName] = null;\n    }\n    if (node.getAttribute(\"style\") === \"\") {\n        node.removeAttribute(\"style\");\n    }\n}\n\n/**\n * @param {string} key\n * @param {object} htmlStyle\n * @returns {string}\n */\nexport function getCSSVariableValue(key, htmlStyle) {\n    // Get trimmed value from the HTML element\n    let value = htmlStyle.getPropertyValue(`--${key}`).trim();\n    // If it is a color value, it needs to be normalized\n    value = normalizeCSSColor(value);\n    // Normally scss-string values are \"printed\" single-quoted. That way no\n    // magic conversation is needed when customizing a variable: either save it\n    // quoted for strings or non quoted for colors, numbers, etc. However,\n    // Chrome has the annoying behavior of changing the single-quotes to\n    // double-quotes when reading them through getPropertyValue...\n    return value.replace(/\"/g, \"'\");\n}\n\n/**\n * Key-value mapping to list converters from an unit A to an unit B.\n * - The key is a string in the format '$1-$2' where $1 is the CSS symbol of\n *   unit A and $2 is the CSS symbol of unit B.\n * - The value is a function that converts the received value (expressed in\n *   unit A) to another value expressed in unit B. Two other parameters is\n *   received: the css property on which the unit applies and the jQuery element\n *   on which that css property may change.\n */\nconst CSS_UNITS_CONVERSION = {\n    \"s-ms\": () => 1000,\n    \"ms-s\": () => 0.001,\n    \"rem-px\": (htmlStyle) => parseFloat(htmlStyle[\"font-size\"]),\n    \"px-rem\": (htmlStyle) => 1 / parseFloat(htmlStyle[\"font-size\"]),\n    \"%-px\": () => -1, // Not implemented but should simply be ignored for now\n    \"px-%\": () => -1, // Not implemented but should simply be ignored for now\n};\n\n/**\n * Converts the given numeric value expressed in the given css unit into\n * the corresponding numeric value expressed in the other given css unit.\n *\n * e.g. fct(400, 'ms', 's') -> 0.4\n *\n * @param {number} value\n * @param {string} unitFrom\n * @param {string} unitTo\n * @param {object} htmlStyle\n * @returns {number}\n */\nexport function convertNumericToUnit(value, unitFrom, unitTo, htmlStyle) {\n    if (Math.abs(value) < Number.EPSILON || unitFrom === unitTo) {\n        return value;\n    }\n    const converter = CSS_UNITS_CONVERSION[`${unitFrom}-${unitTo}`];\n    if (converter === undefined) {\n        throw new Error(`Cannot convert '${unitFrom}' units into '${unitTo}' units !`);\n    }\n    return value * converter(htmlStyle);\n}\n\nexport function getHtmlStyle(document) {\n    return document.defaultView.getComputedStyle(document.documentElement);\n}\n\n/**\n * Finds the font size to display for the current selection. We cannot rely\n * on the computed font-size only as font-sizes are responsive and we always\n * want to display the desktop (integer when possible) one.\n *\n * @param {Selection} sel The current selection.\n * @param {Document} document The document of the current selection.\n * @returns {Float} The font size to display.\n */\nexport function getFontSizeDisplayValue(sel, document) {\n    const tagNameRelatedToFontSize = [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"];\n    const styleClassesRelatedToFontSize = [\n        \"display-1\",\n        \"display-2\",\n        \"display-3\",\n        \"display-4\",\n        \"lead\",\n    ];\n    const closestStartContainerEl = closestElement(sel.startContainer);\n    const closestFontSizedEl = closestStartContainerEl.closest(`\n        [style*='font-size'],\n        ${FONT_SIZE_CLASSES.map((className) => `.${className}`)},\n        ${styleClassesRelatedToFontSize.map((className) => `.${className}`)},\n        ${tagNameRelatedToFontSize}\n    `);\n    let remValue;\n    const htmlStyle = getHtmlStyle(document);\n    if (closestFontSizedEl) {\n        const useFontSizeInput = closestFontSizedEl.style.fontSize;\n        if (useFontSizeInput) {\n            // Use the computed value to always convert to px. However, this\n            // currently does not check that the inline font-size is the one\n            // actually having an effect (there could be an !important CSS rule\n            // forcing something else).\n            // TODO align with the behavior of the rest of the editor snippet\n            // options.\n            return parseFloat(getComputedStyle(closestStartContainerEl).fontSize);\n        }\n        // It's a class font size or a hN tag. We don't return the computed\n        // font size because it can be different from the one displayed in\n        // the toolbar because it's responsive.\n        const fontSizeClass = FONT_SIZE_CLASSES.find((className) =>\n            closestFontSizedEl.classList.contains(className)\n        );\n        let fsName;\n        if (fontSizeClass) {\n            fsName = fontSizeClass.substring(0, fontSizeClass.length - 3); // Without -fs\n        } else {\n            fsName =\n                styleClassesRelatedToFontSize.find((className) =>\n                    closestFontSizedEl.classList.contains(className)\n                ) || closestFontSizedEl.tagName.toLowerCase();\n        }\n        remValue = parseFloat(getCSSVariableValue(`${fsName}-font-size`, htmlStyle));\n    }\n    const pxValue = remValue && convertNumericToUnit(remValue, \"rem\", \"px\", htmlStyle);\n    return pxValue || parseFloat(getComputedStyle(closestStartContainerEl).fontSize);\n}\n\nexport function getFontSizeOrClass(node) {\n    if (!node) {\n        return null;\n    }\n\n    if (node.style.fontSize) {\n        return { type: \"font-size\", value: node.style.fontSize };\n    }\n\n    const fontSizeClass = FONT_SIZE_CLASSES.find((cls) => node.classList.contains(cls));\n    if (fontSizeClass) {\n        return { type: \"class\", value: fontSizeClass };\n    }\n    return null;\n}\n", "/**\n * Creates a version of the function that's memoized on the value of its first\n * argument, which must be an Object.\n * This is a version of @web's memoize, with the difference that it uses a\n * WeakMap instead of a Map, making it more suitable for functions that take\n * objects as arguments, as it avoids memory leaks by allowing the garbage\n * collector to clean up unused objects.\n *\n * @template T, U\n * @param {(arg: T) => U} func the function to memoize\n * @returns {(arg: T) => U} a memoized version of the original function\n */\nexport function weakMemoize(func) {\n    const cache = new WeakMap();\n    const funcName = func.name ? func.name + \" (memoized)\" : \"memoized\";\n    return {\n        [funcName](firstArg, ...args) {\n            if (!cache.has(firstArg)) {\n                cache.set(firstArg, func(firstArg, ...args));\n            }\n            return cache.get(firstArg);\n        },\n    }[funcName];\n}\n", "/**\n * @param { Document } document\n * @param { string } html\n * @returns { DocumentFragment }\n */\nexport function parseHTML(document, html) {\n    const fragment = document.createDocumentFragment();\n    const parser = new document.defaultView.DOMParser();\n    const parsedDocument = parser.parseFromString(html, \"text/html\");\n    fragment.replaceChildren(...parsedDocument.body.childNodes);\n    return fragment;\n}\n\n/**\n * Server-side, HTML is stored as a string which can have a different format\n * than what the current browser returns through outerHTML or innerHTML, notably\n * because of HTML entities.\n * This function can be used to convert strings with potential HTML entities to\n * the format used by the current browser. This allows comparisons between\n * values returned by the server and values extracted from the DOM using i.e.\n * innerHTML.\n *\n * @param { string } content\n * @param { function } cleanup receives the body element containing the parsed\n *        html, to perform some cleanup for the comparison.\n * @returns { string }\n */\nexport function normalizeHTML(content, cleanup = () => {}) {\n    const parser = new document.defaultView.DOMParser();\n    const body = parser.parseFromString(content, \"text/html\").body;\n    cleanup(body);\n    return body.innerHTML;\n}\n", "import { isColorGradient } from \"@web/core/utils/colors\";\n\n/**\n * Extracts url and gradient parts from the background-image CSS property.\n *\n * @param {string} CSS 'background-image' property value\n * @returns {Object} contains the separated 'url' and 'gradient' parts\n */\nexport function backgroundImageCssToParts(css = \"\") {\n    const parts = {};\n    if (css.startsWith(\"url(\")) {\n        const urlEnd = css.indexOf(\")\") + 1;\n        parts.url = css.substring(0, urlEnd).trim();\n        const commaPos = css.indexOf(\",\", urlEnd);\n        css = commaPos > 0 ? css.substring(commaPos + 1) : \"\";\n    }\n    if (isColorGradient(css)) {\n        parts.gradient = css.trim();\n    }\n    return parts;\n}\n\n/**\n * Combines url and gradient parts into a background-image CSS property value\n *\n * @param {Object} contains the separated 'url' and 'gradient' parts\n * @returns {string} CSS 'background-image' property value\n */\nexport function backgroundImagePartsToCss(parts) {\n    return [parts.url, parts.gradient].filter(Boolean).join(\", \") || \"\";\n}\n\n/**\n * @param {HTMLImageElement} image\n * @returns {string|null} The mimetype of the image.\n */\nexport function getMimetype(image, data = image.dataset) {\n    const src = getImageSrc(image);\n\n    return (\n        data.mimetype ||\n        data.mimetypeBeforeConversion ||\n        (src &&\n            ((src.endsWith(\".png\") && \"image/png\") ||\n                (src.endsWith(\".webp\") && \"image/webp\") ||\n                (src.endsWith(\".jpg\") && \"image/jpeg\") ||\n                (src.endsWith(\".jpeg\") && \"image/jpeg\"))) ||\n        null\n    );\n}\n\n/**\n * @param {HTMLImageElement} img\n * @returns {Promise<Boolean>}\n */\nexport async function isImageCorsProtected(img) {\n    const src = img.getAttribute(\"src\");\n    if (!src) {\n        return false;\n    }\n    let isCorsProtected = false;\n    if (!src.startsWith(\"/\") || /\\/web\\/image\\/\\d+-redirect\\//.test(src)) {\n        // The `fetch()` used later in the code might fail if the image is\n        // CORS protected. We check upfront if it's the case.\n        // Two possible cases:\n        // 1. the `src` is an absolute URL from another domain.\n        //    For instance, abc.odoo.com vs abc.com which are actually the\n        //    same database behind.\n        // 2. A \"attachment-url\" which is just a redirect to the real image\n        //    which could be hosted on another website.\n        isCorsProtected = await fetch(src, { method: \"HEAD\" })\n            .then(() => false)\n            .catch(() => true);\n    }\n    return isCorsProtected;\n}\n\n/**\n * @param {string} src\n * @returns {Promise<Boolean>}\n */\nexport async function isSrcCorsProtected(src) {\n    const dummyImg = document.createElement(\"img\");\n    dummyImg.src = src;\n    return isImageCorsProtected(dummyImg);\n}\n\n/**\n * Returns the src of the image, or the src of the background-image if the\n * element is not an image.\n *\n * @param {HTMLElement} el The element to get the src or background-image from.\n * @returns {string|null} The src of the image.\n */\nexport function getImageSrc(el) {\n    if (el.tagName === \"IMG\") {\n        return el.getAttribute(\"src\");\n    }\n    // TODO: Parallax handling is incorrectly coupled with background image source.\n    // The plugin transfer the `src` on a `span`, but parallax can be achieved via other means.\n    // example: CSS variables without this DOM manipulation.\n    // Decouple.\n    if (el.querySelector(\".s_parallax_bg\")) {\n        el = el.querySelector(\".s_parallax_bg\");\n    }\n    const url = backgroundImageCssToParts(el.style.backgroundImage).url;\n    return url && getBgImageURLFromURL(url);\n}\n\n/**\n * Parse an element's background-image's url.\n *\n * @param {string} string a css value in the form 'url(\"...\")'\n * @returns {string|false} the src of the image or false if not parsable\n */\nexport function getBgImageURLFromURL(url) {\n    const match = url.match(/^url\\((['\"])(.*?)\\1\\)$/);\n    if (!match) {\n        return \"\";\n    }\n    const matchedURL = match[2];\n    // Make URL relative if possible\n    const fullURL = new URL(matchedURL, window.location.origin);\n    if (fullURL.origin === window.location.origin) {\n        return fullURL.href.slice(fullURL.origin.length);\n    }\n    return matchedURL;\n}\n", "import { rpc } from \"@web/core/network/rpc\";\nimport { pick } from \"@web/core/utils/objects\";\nimport { loadBundle } from \"@web/core/assets\";\nimport { getImageSrc } from \"./image\";\n\n// Fields returned by cropperjs 'getData' method, also need to be passed when\n// initializing the cropper to reuse the previous crop.\nexport const cropperDataFields = [\"x\", \"y\", \"width\", \"height\", \"rotate\", \"scaleX\", \"scaleY\"];\nexport const cropperDataFieldsWithAspectRatio = [...cropperDataFields, \"aspectRatio\"];\nexport const isGif = (mimetype) => mimetype === \"image/gif\";\n\nlet _isWebGLEnabled;\n/**\n * Cacheable check telling whether the current browser can allocate a WebGL context.\n */\nexport function isWebGLEnabled() {\n    if (_isWebGLEnabled !== undefined) {\n        return _isWebGLEnabled;\n    }\n    try {\n        const canvas = document.createElement(\"canvas\");\n        _isWebGLEnabled = !!(\n            window.WebGLRenderingContext &&\n            (canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\"))\n        );\n    } catch {\n        _isWebGLEnabled = false;\n    }\n    return _isWebGLEnabled;\n}\n\nconst modifierFields = [\n    \"filter\",\n    \"quality\",\n    \"mimetype\",\n    \"glFilter\",\n    \"originalId\",\n    \"originalSrc\",\n    \"resizeWidth\",\n    \"aspectRatio\",\n    \"mimetypeBeforeConversion\",\n];\n\nexport const removeOnImageChangeAttrs = [...cropperDataFields, ...modifierFields];\n\nconst cache = {};\n\nconst placeholderHref = \"/web/image/__odoo__unknown__src__/\";\n\nfunction _getValidSrc(src) {\n    if (src in cache) {\n        return cache[src];\n    }\n    const prom = new Promise((resolve) => {\n        fetch(src)\n            .then((response) => {\n                resolve(response.ok ? src : placeholderHref);\n            })\n            .catch(() => {\n                resolve(placeholderHref);\n            });\n    });\n    cache[src] = prom;\n    return prom;\n}\n\n/**\n * Loads an src into an HTMLImageElement.\n *\n * @param {String} src URL of the image to load\n * @param {HTMLImageElement} [img] img element in which to load the image\n * @returns {Promise<HTMLImageElement>} Promise that resolves to the loaded img\n *     or a placeholder image if the src is not found.\n */\nexport async function loadImage(src, img = new Image()) {\n    const source = await _getValidSrc(src);\n    return new Promise((resolve, reject) => {\n        img.addEventListener(\"load\", () => resolve(img), { once: true });\n        img.addEventListener(\"error\", reject, { once: true });\n        img.src = source;\n    });\n}\n\n// Because cropperjs acquires images through XHRs on the image src and we don't\n// want to load big images over the network many times when adjusting quality\n// and filter, we create a local cache of the images using object URLs.\nconst imageCache = new Map();\n\n/**\n * Loads image object URL into cache if not already set and returns it.\n *\n * @param {String} src\n * @returns {Promise}\n */\nfunction _loadImageObjectURL(src) {\n    return _updateImageData(src);\n}\n\n/**\n * Gets image dataURL from cache in the same way as object URL.\n *\n * @param {String} src\n * @returns {Promise}\n */\nexport function loadImageDataURL(src) {\n    return _updateImageData(src, \"dataURL\");\n}\n\n/**\n * @param {String} src used as a key on the image cache map.\n * @param {String} [key='objectURL'] specifies the image data to update/return.\n * @returns {Promise<String>} resolves with either dataURL/objectURL value.\n */\nasync function _updateImageData(src, key = \"objectURL\") {\n    const currentImageData = imageCache.get(src);\n    if (currentImageData && currentImageData[key]) {\n        return currentImageData[key];\n    }\n    let value = \"\";\n    const blob = await fetch(src).then((res) => res.blob());\n    if (key === \"dataURL\") {\n        value = await createDataURL(blob);\n    } else {\n        value = URL.createObjectURL(blob);\n    }\n    imageCache.set(src, Object.assign(currentImageData || {}, { [key]: value, size: blob.size }));\n    return value;\n}\n\n/**\n * Returns the size of a cached image.\n * Warning: this supposes that the image is already in the cache, i.e. that\n * _updateImageData was called before.\n *\n * @param {String} src used as a key on the image cache map.\n * @returns {Number} size of the image in bytes.\n */\nexport function getImageSizeFromCache(src) {\n    return imageCache.get(src).size;\n}\n\n/**\n * Activates the cropper on a given image.\n *\n * @param {jQuery} $image the image on which to activate the cropper\n * @param {Number} aspectRatio the aspectRatio of the crop box\n * @param {DOMStringMap} dataset dataset containing the cropperDataFields\n */\nexport async function activateCropper(image, aspectRatio, dataset) {\n    await loadBundle(\"html_editor.assets_image_cropper\");\n    const oldSrc = image.src;\n    const newSrc = await _loadImageObjectURL(image.getAttribute(\"src\"));\n    image.src = newSrc;\n    let readyResolve;\n    const readyPromise = new Promise((resolve) => (readyResolve = resolve));\n    // eslint-disable-next-line no-undef\n    const cropper = new Cropper(image, {\n        viewMode: 2,\n        dragMode: \"move\",\n        autoCropArea: 1.0,\n        aspectRatio: aspectRatio,\n        data: Object.fromEntries(\n            Object.entries(pick(dataset, ...cropperDataFields)).map(([key, value]) => [\n                key,\n                parseFloat(value),\n            ])\n        ),\n        // Can't use 0 because it's falsy and cropperjs will then use its defaults (200x100)\n        minContainerWidth: 1,\n        minContainerHeight: 1,\n        ready: readyResolve,\n    });\n    if (oldSrc === newSrc && image.complete) {\n        return;\n    }\n    await readyPromise;\n    return cropper;\n}\n\n/**\n * Marks an <img> with its attachment data (originalId, originalSrc, mimetype)\n *\n * @param {HTMLElement} el\n * @param {string} [attachmentSrc=''] specifies the URL of the corresponding\n * attachment if it can't be found in the 'src' attribute.\n */\nexport async function loadImageInfo(el, attachmentSrc = \"\") {\n    const newDataset = {};\n    const elSrc = getImageSrc(el);\n\n    const src = attachmentSrc || elSrc;\n    // If there is a marked originalSrc, the data is already loaded.\n    // If the image does not have the \"mimetypeBeforeConversion\" attribute, it\n    // has to be added.\n    if ((el.dataset.originalSrc && el.dataset.mimetypeBeforeConversion) || !src) {\n        return newDataset;\n    }\n    // In order to be robust to absolute, relative and protocol relative URLs,\n    // the src of the img is first converted to an URL object. To do so, the URL\n    // of the document in which the img is located is used as a base to build\n    // the URL object if the src of the img is a relative or protocol relative\n    // URL. The original attachment linked to the img is then retrieved thanks\n    // to the path of the built URL object.\n    let docHref = el.ownerDocument.defaultView.location.href;\n    if (docHref.startsWith(\"about:\")) {\n        docHref = window.location.href;\n    }\n\n    const srcUrl = new URL(src, docHref);\n    let relativeSrc = decodeURI(srcUrl.pathname);\n\n    let match = relativeSrc.match(/\\/(?:web_editor|html_editor)\\/image_shape\\/(\\w+\\.\\w+)/);\n    if (el.dataset.shape && match) {\n        match = match[1];\n        if (match.endsWith(\"_perspective\")) {\n            // As an image might already have been modified with a\n            // perspective for some customized snippets in themes. We need\n            // to find the original image to set the 'data-original-src'\n            // attribute.\n            match = match.slice(0, -12);\n        }\n        relativeSrc = `/web/image/${encodeURIComponent(match)}`;\n    }\n\n    const { original } = await rpc(\n        \"/html_editor/get_image_info\",\n        { src: relativeSrc },\n        { cache: true }\n    );\n    // If src was an absolute \"external\" URL, we consider unlikely that its\n    // relative part matches something from the DB and even if it does, nothing\n    // bad happens, besides using this random image as the original when using\n    // the options, instead of having no option. Note that we do not want to\n    // check if the image is local or not here as a previous bug converted some\n    // local (relative src) images to absolute URL... and that before users had\n    // setup their website domain. That means they can have an absolute URL that\n    // looks like \"https://mycompany.odoo.com/web/image/123\" that leads to a\n    // \"local\" image even if the domain name is now \"mycompany.be\".\n    //\n    // The \"redirect\" check is for when it is a redirect image attachment due to\n    // an external URL upload.\n    if (\n        original &&\n        original.image_src &&\n        !/\\/web\\/image\\/\\d+-redirect\\//.test(original.image_src)\n    ) {\n        newDataset.originalId = original.id;\n        newDataset.originalSrc = original.image_src;\n        newDataset.mimetypeBeforeConversion = original.mimetype;\n    }\n    return newDataset;\n}\n\n/**\n * @param {Blob} blob\n * @returns {Promise}\n */\nexport function createDataURL(blob) {\n    return new Promise((resolve, reject) => {\n        const reader = new FileReader();\n        reader.addEventListener(\"load\", () => resolve(reader.result));\n        reader.addEventListener(\"abort\", reject);\n        reader.addEventListener(\"error\", reject);\n        reader.readAsDataURL(blob);\n    });\n}\n\n/**\n * @param {String} dataURL\n * @returns {Number} number of bytes represented with base64\n */\nexport function getDataURLBinarySize(dataURL) {\n    // Every 4 bytes of base64 represent 3 bytes.\n    return (dataURL.split(\",\")[1].length / 4) * 3;\n}\n\n/**\n * Returns the aspect ratio from a string or number.\n * If the input is a string, it can be a ratio (e.g. \"16:9\") or a single number.\n * If the input is a number, it is returned as is.\n *\n * @param {string|number} ratio\n * @returns {number}\n */\nexport function getAspectRatio(ratio) {\n    if (typeof ratio === \"number\") {\n        return ratio;\n    }\n    const [a, b] = ratio.split(/[:/]/).map((n) => parseFloat(n));\n    // If the ratio is invalid, return only a.\n    if (!b) {\n        return a;\n    }\n    return a / b;\n}\n", "/**\n * Transform a 2D point using a projective transformation matrix. Note that\n * this method is only well behaved for points that don't map to infinity!\n *\n * @param {number[][]} matrix - A projective transformation matrix\n * @param {number[]} point - A 2D point\n * @returns The transformed 2D point\n */\nexport function transform([[a, b, c], [d, e, f], [g, h, i]], [x, y]) {\n    const z = g * x + h * y + i;\n    return [(a * x + b * y + c) / z, (d * x + e * y + f) / z];\n}\n\n/**\n * Calculate the inverse of a 3x3 matrix assuming it is invertible.\n *\n * @param {number[][]} matrix - A 3x3 matrix\n * @returns The resulting 3x3 matrix\n */\nfunction invert([[a, b, c], [d, e, f], [g, h, i]]) {\n    const determinant = a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;\n    return [\n        [\n            (e * i - h * f) / determinant,\n            (h * c - b * i) / determinant,\n            (b * f - e * c) / determinant,\n        ],\n        [\n            (g * f - d * i) / determinant,\n            (a * i - g * c) / determinant,\n            (d * c - a * f) / determinant,\n        ],\n        [\n            (d * h - g * e) / determinant,\n            (g * b - a * h) / determinant,\n            (a * e - d * b) / determinant,\n        ],\n    ];\n}\n\n/**\n * Multiply two 3x3 matrices.\n *\n * @param {number[][]} a - A 3x3 matrix\n * @param {number[][]} b - A 3x3 matrix\n * @returns The resulting 3x3 matrix\n */\nfunction multiply(a, b) {\n    const [[a0, a1, a2], [a3, a4, a5], [a6, a7, a8]] = a;\n    const [[b0, b1, b2], [b3, b4, b5], [b6, b7, b8]] = b;\n    return [\n        [a0 * b0 + a1 * b3 + a2 * b6, a0 * b1 + a1 * b4 + a2 * b7, a0 * b2 + a1 * b5 + a2 * b8],\n        [a3 * b0 + a4 * b3 + a5 * b6, a3 * b1 + a4 * b4 + a5 * b7, a3 * b2 + a4 * b5 + a5 * b8],\n        [a6 * b0 + a7 * b3 + a8 * b6, a6 * b1 + a7 * b4 + a8 * b7, a6 * b2 + a7 * b5 + a8 * b8],\n    ];\n}\n\n/**\n * Find a projective transformation mapping a rectangular area at origin (0,0)\n * with a given width and height to a certain quadrilateral.\n *\n * @param {number} width - The width of the rectangular area\n * @param {number} height - The height of the rectangular area\n * @param {number[][]} quadrilateral - The vertices of the quadrilateral\n * @returns A projective transformation matrix\n */\nexport function getProjective(width, height, [[x0, y0], [x1, y1], [x2, y2], [x3, y3]]) {\n    // Calculate a set of homogeneous coordinates a, b, c of the first\n    // point using the other three points as basis vectors in the\n    // underlying vector space.\n    const denominator = x3 * (y1 - y2) + x1 * (y2 - y3) + x2 * (y3 - y1);\n    const a = (x0 * (y2 - y3) + x2 * (y3 - y0) + x3 * (y0 - y2)) / denominator;\n    const b = (x0 * (y3 - y1) + x3 * (y1 - y0) + x1 * (y0 - y3)) / denominator;\n    const c = (x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1)) / denominator;\n\n    // The reverse transformation maps the homogeneous coordinates of\n    // the last three corners of the original image onto the basis vectors\n    // while mapping the first corner onto (1, 1, 1). The forward\n    // transformation maps those basis vectors in addition to (1, 1, 1)\n    // onto homogeneous coordinates of the corresponding corners of the\n    // projective image. Combining these together yields the projective\n    // transformation we are looking for.\n    const reverse = invert([\n        [width, -width, 0],\n        [0, -height, height],\n        [1, -1, 1],\n    ]);\n    const forward = [\n        [a * x1, b * x2, c * x3],\n        [a * y1, b * y2, c * y3],\n        [a, b, c],\n    ];\n\n    return multiply(forward, reverse);\n}\n\n/**\n * Find an affine transformation matrix that exactly maps the vertices of a\n * triangle to their corresponding images of a projective transformation. The\n * resulting transformation will be an approximation of the projective\n * transformation for the area inside the triangle.\n *\n * @param {number[][]} projective - A projective transformation matrix\n * @param {number[][]} triangle - The vertices of a triangle\n * @returns - An affine transformation matrix\n */\nexport function getAffineApproximation(projective, [[x0, y0], [x1, y1], [x2, y2]]) {\n    const a = transform(projective, [x0, y0]);\n    const b = transform(projective, [x1, y1]);\n    const c = transform(projective, [x2, y2]);\n\n    return multiply(\n        [\n            [a[0], b[0], c[0]],\n            [a[1], b[1], c[1]],\n            [1, 1, 1],\n        ],\n        invert([\n            [x0, x1, x2],\n            [y0, y1, y2],\n            [1, 1, 1],\n        ])\n    );\n}\n", "// Position and sizes\n//------------------------------------------------------------------------------\n\nexport const DIRECTIONS = {\n    LEFT: false,\n    RIGHT: true,\n};\n\n/**\n * @param {Node} node\n * @returns {[HTMLElement, number]}\n */\nexport function leftPos(node) {\n    return [node.parentElement, childNodeIndex(node)];\n}\n/**\n * @param {Node} node\n * @returns {[HTMLElement, number]}\n */\nexport function rightPos(node) {\n    return [node.parentElement, childNodeIndex(node) + 1];\n}\n/**\n * @param {Node} node\n * @returns {[HTMLElement, number, HTMLElement, number]}\n */\nexport function boundariesOut(node) {\n    const index = childNodeIndex(node);\n    return [node.parentElement, index, node.parentElement, index + 1];\n}\n/**\n * @param {Node} node\n * @returns {[HTMLElement, number, HTMLElement, number]}\n */\nexport function boundariesIn(node) {\n    return [node, 0, node, nodeSize(node)];\n}\n/**\n * @param {Node} node\n * @returns {[Node, number]}\n */\nexport function startPos(node) {\n    return [node, 0];\n}\n/**\n * @param {Node} node\n * @returns {[Node, number]}\n */\nexport function endPos(node) {\n    return [node, nodeSize(node)];\n}\n/**\n * Returns the given node's position relative to its parent (= its index in the\n * child nodes of its parent).\n *\n * @param {Node} node\n * @returns {number}\n */\nexport function childNodeIndex(node) {\n    let i = 0;\n    while (node.previousSibling) {\n        i++;\n        node = node.previousSibling;\n    }\n    return i;\n}\n/**\n * Returns the size of the node = the number of characters for text nodes and\n * the number of child nodes for element nodes.\n *\n * @param {Node} node\n * @returns {number}\n */\nexport function nodeSize(node) {\n    const isTextNode = node.nodeType === Node.TEXT_NODE;\n    if (isTextNode) {\n        return node.length;\n    } else {\n        const child = node.lastChild;\n        return child ? childNodeIndex(child) + 1 : 0;\n    }\n}\n", "/* eslint-disable */\n\nconst tldWhitelist = [\n    'com', 'net', 'org', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an',\n    'ao', 'aq', 'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd',\n    'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bl', 'bm', 'bn', 'bo', 'br', 'bq',\n    'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc', 'cd', 'cf', 'cg', 'ch',\n    'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv', 'cw', 'cx',\n    'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg',\n    'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga',\n    'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq',\n    'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu',\n    'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm',\n    'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky',\n    'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly',\n    'ma', 'mc', 'md', 'me', 'mf', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo',\n    'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na',\n    'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om',\n    'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt',\n    'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd',\n    'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'ss',\n    'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj',\n    'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua',\n    'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn',\n    'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', 'zr', 'zw', 'co\\\\.uk'];\n\nconst urlRegexBase = `|(?:www.))[-a-zA-Z0-9@:%._\\\\+~#=]{2,256}\\\\.[a-zA-Z][a-zA-Z0-9]{1,62}|(?:[-a-zA-Z0-9@:%._\\\\+~#=]{2,256}\\\\.(?:${tldWhitelist.join('|')})\\\\b))(?:(?:[/?#])[^\\\\s]*[^!.,})\\\\]'\"\\\\s]|(?:[^!(){}.,[\\\\]'\"\\\\s]+))?`;\nconst httpCapturedRegex= `(https?:\\\\/\\\\/)`;\n\nexport const URL_REGEX = new RegExp(`((?:(?:${httpCapturedRegex}${urlRegexBase})`, 'i');\n", "export const resourceSequenceSymbol = Symbol(\"resourceSequence\");\n\n/**\n * @template T\n * @typedef {Object} ResourceWithSequence\n * @property {T} object\n */\n\n/**\n * @template T\n * @param {number} sequenceNumber\n * @param {T} object\n * @returns {ResourceWithSequence<T>}\n */\nexport function withSequence(sequenceNumber, object) {\n    if (typeof sequenceNumber !== \"number\") {\n        throw new Error(\n            `sequenceNumber must be a number. Got ${sequenceNumber} (${typeof sequenceNumber}).`\n        );\n    }\n    return {\n        [resourceSequenceSymbol]: sequenceNumber,\n        object,\n    };\n}\n", "import { containsAnyInline } from \"./dom_info\";\nimport { wrapInlinesInBlocks } from \"./dom\";\nimport { markup } from \"@odoo/owl\";\nimport { htmlReplace } from \"@web/core/utils/html\";\n\nexport function initElementForEdition(element, options = {}) {\n    if (\n        element?.nodeType === Node.ELEMENT_NODE &&\n        containsAnyInline(element) &&\n        !options.allowInlineAtRoot\n    ) {\n        // No matter the inline content, it will be wrapped in a DIV to try\n        // and match the current style of the content as much as possible.\n        // (P has a margin-bottom, DIV does not).\n        wrapInlinesInBlocks(element, {\n            baseContainerNodeName: \"DIV\",\n        });\n    }\n\n    // During `convert_inline`, image elements may receive `width` and `height` attributes,\n    // along with inline styles. These attributes force specific dimensions, which breaks\n    // the fallback to default sizing. We remove them here to allow proper resizing behavior.\n    // The attributes will be re-applied on save.\n    for (const img of element.querySelectorAll(\"img[width], img[height]\")) {\n        const width = img.getAttribute(\"width\");\n        const height = img.getAttribute(\"height\");\n        img.removeAttribute(\"height\");\n        img.removeAttribute(\"width\");\n        img.style.setProperty(\"width\", isNaN(width) ? width : `${width}px`);\n        img.style.setProperty(\"height\", isNaN(height) ? height : `${height}px`);\n    }\n}\n\n/**\n * Converts XML-style self-closing tags (e.g., <div/> <span/> <t/>) into proper\n * HTML start/end tag pairs, except for true HTML void elements.\n *\n * @param {string | ReturnType<markup>} content\n * @returns {ReturnType<markup>}\n */\nexport function fixInvalidHTML(content) {\n    if (!content) {\n        return content;\n    }\n    // Match self-closing tags EXCEPT HTML void elements.\n    // We do not use selfClosingElementTags because it includes XML-only tags\n    // such as <t>, which must not be treated as void in HTML.\n    const regex =\n        /<\\s*(?!area\\b|base\\b|br\\b|col\\b|embed\\b|hr\\b|img\\b|input\\b|link\\b|meta\\b|param\\b|source\\b|track\\b|wbr\\b)([a-zA-Z0-9:-]+)\\s*((?:(?:\\s+[\\w:-]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'=<>`]+))?)*))\\s*\\/>/g;\n    return htmlReplace(content, regex, (match, tag, attributes) => {\n        // markup: content is either already markup or escaped in htmlReplace\n        attributes = markup(attributes);\n        return markup`<${tag}${attributes}></${tag}>`;\n    });\n}\n\nlet Markup = null;\n\nexport function instanceofMarkup(value) {\n    if (!Markup) {\n        Markup = markup(\"\").constructor;\n    }\n    return value instanceof Markup;\n}\n", "import { closestBlock, isBlock } from \"./blocks\";\nimport {\n    getDeepestPosition,\n    isContentEditable,\n    isNotEditableNode,\n    isSelfClosingElement,\n    nextLeaf,\n    previousLeaf,\n} from \"./dom_info\";\nimport { isFakeLineBreak } from \"./dom_state\";\nimport { closestElement, createDOMPathGenerator } from \"./dom_traversal\";\nimport {\n    DIRECTIONS,\n    childNodeIndex,\n    endPos,\n    leftPos,\n    nodeSize,\n    rightPos,\n    startPos,\n} from \"./position\";\n\n/**\n * @typedef { import(\"./selection_plugin\").EditorSelection } EditorSelection\n */\n\n/**\n * From selection position, checks if it is left-to-right or right-to-left.\n *\n * @param {Node} anchorNode\n * @param {number} anchorOffset\n * @param {Node} focusNode\n * @param {number} focusOffset\n * @returns {boolean} the direction of the current range if the selection not is collapsed | false\n */\nexport function getCursorDirection(anchorNode, anchorOffset, focusNode, focusOffset) {\n    if (anchorNode === focusNode) {\n        if (anchorOffset === focusOffset) {\n            return false;\n        }\n        return anchorOffset < focusOffset ? DIRECTIONS.RIGHT : DIRECTIONS.LEFT;\n    }\n    return anchorNode.compareDocumentPosition(focusNode) & Node.DOCUMENT_POSITION_FOLLOWING\n        ? DIRECTIONS.RIGHT\n        : DIRECTIONS.LEFT;\n}\n\n/**\n * @param {EditorSelection} selection\n * @param {string} selector\n */\nexport function findInSelection(selection, selector) {\n    const selectorInStartAncestors = closestElement(selection.startContainer, selector);\n    if (selectorInStartAncestors) {\n        return selectorInStartAncestors;\n    } else {\n        const commonElementAncestor = closestElement(selection.commonAncestorContainer);\n        return (\n            commonElementAncestor &&\n            [...commonElementAncestor.querySelectorAll(selector)].find((node) =>\n                selection.intersectsNode(node)\n            )\n        );\n    }\n}\n\nconst leftLeafOnlyInScopeNotBlockEditablePath = createDOMPathGenerator(DIRECTIONS.LEFT, {\n    leafOnly: true,\n    inScope: true,\n    stopTraverseFunction: (node) => isNotEditableNode(node) || isBlock(node),\n    stopFunction: (node) => isNotEditableNode(node) || isBlock(node),\n});\n\nconst rightLeafOnlyInScopeNotBlockEditablePath = createDOMPathGenerator(DIRECTIONS.RIGHT, {\n    leafOnly: true,\n    inScope: true,\n    stopTraverseFunction: (node) => isNotEditableNode(node) || isBlock(node),\n    stopFunction: (node) => isNotEditableNode(node) || isBlock(node),\n});\n\nexport function normalizeSelfClosingElement(node, offset) {\n    if (isSelfClosingElement(node)) {\n        // Cannot put cursor inside those elements, put it after instead.\n        [node, offset] = rightPos(node);\n    }\n    return [node, offset];\n}\n\nexport function normalizeNotEditableNode(node, offset, position = \"right\") {\n    const editable = closestElement(node, \".odoo-editor-editable\");\n    let closest = closestElement(node);\n    while (closest && closest !== editable && !closest.isContentEditable) {\n        [node, offset] = position === \"right\" ? rightPos(node) : leftPos(node);\n        closest = node;\n    }\n    return [node, offset];\n}\n\nexport function normalizeCursorPosition(node, offset, position = \"right\") {\n    [node, offset] = normalizeSelfClosingElement(node, offset);\n    [node, offset] = normalizeNotEditableNode(node, offset, position);\n    // todo @phoenix: we should maybe remove it\n    // // Be permissive about the received offset.\n    // offset = Math.min(Math.max(offset, 0), nodeSize(node));\n    return [node, offset];\n}\n\nexport function normalizeFakeBR(node, offset) {\n    const prevNode = node.nodeType === Node.ELEMENT_NODE && node.childNodes[offset - 1];\n    if (prevNode && prevNode.nodeName === \"BR\" && isFakeLineBreak(prevNode)) {\n        // If trying to put the cursor on the right of a fake line break, put\n        // it before instead.\n        offset--;\n    }\n    return [node, offset];\n}\n\n/**\n * From a given position, returns the normalized version.\n *\n * E.g. <b>abc</b>[]def -> <b>abc[]</b>def\n *\n * @param {Node} node\n * @param {number} offset\n * @returns { [Node, number] }\n */\nexport function normalizeDeepCursorPosition(node, offset) {\n    // Put the cursor in deepest inline node around the given position if\n    // possible.\n    let el;\n    let elOffset;\n    if (node.nodeType === Node.ELEMENT_NODE) {\n        el = node;\n        elOffset = offset;\n    } else if (node.nodeType === Node.TEXT_NODE) {\n        if (offset === 0) {\n            el = node.parentNode;\n            elOffset = childNodeIndex(node);\n        } else if (offset === node.length) {\n            el = node.parentNode;\n            elOffset = childNodeIndex(node) + 1;\n        }\n    }\n    if (el) {\n        const leftInlineNode = leftLeafOnlyInScopeNotBlockEditablePath(el, elOffset).next().value;\n        let leftVisibleEmpty = false;\n        if (leftInlineNode) {\n            leftVisibleEmpty =\n                isSelfClosingElement(leftInlineNode) || !isContentEditable(leftInlineNode);\n            [node, offset] = leftVisibleEmpty ? rightPos(leftInlineNode) : endPos(leftInlineNode);\n        }\n        if (!leftInlineNode || leftVisibleEmpty) {\n            const rightInlineNode = rightLeafOnlyInScopeNotBlockEditablePath(el, elOffset).next()\n                .value;\n            if (rightInlineNode) {\n                const closest = closestElement(rightInlineNode);\n                const rightVisibleEmpty =\n                    isSelfClosingElement(rightInlineNode) || !closest || !closest.isContentEditable;\n                if (!(leftVisibleEmpty && rightVisibleEmpty)) {\n                    [node, offset] = rightVisibleEmpty\n                        ? leftPos(rightInlineNode)\n                        : startPos(rightInlineNode);\n                }\n            }\n        }\n    }\n    return [node, offset];\n}\n\nfunction updateCursorBeforeMove(destParent, destIndex, node, cursor) {\n    if (cursor.node === destParent && cursor.offset >= destIndex) {\n        // Update cursor at destination\n        cursor.offset += 1;\n    } else if (cursor.node === node.parentNode) {\n        const childIndex = childNodeIndex(node);\n        // Update cursor at origin\n        if (cursor.offset === childIndex && cursor.offset === 0) {\n            // Keep cursor before the moved node if it's the first child before the move\n            [cursor.node, cursor.offset] = [destParent, destIndex];\n        } else if (cursor.offset === childIndex + 1 && cursor.offset === nodeSize(cursor.node)) {\n            // Keep cursor after the moved node if it's the last child before the move\n            [cursor.node, cursor.offset] = [destParent, destIndex + 1];\n        } else if (cursor.offset > childIndex) {\n            cursor.offset -= 1;\n        }\n    }\n}\n\nfunction updateCursorBeforeRemove(node, cursor) {\n    if (node.contains(cursor.node)) {\n        [cursor.node, cursor.offset] = [node.parentNode, childNodeIndex(node)];\n    } else if (cursor.node === node.parentNode && cursor.offset > childNodeIndex(node)) {\n        cursor.offset -= 1;\n    }\n}\n\nfunction updateCursorBeforeUnwrap(node, cursor) {\n    if (cursor.node === node) {\n        [cursor.node, cursor.offset] = [node.parentNode, cursor.offset + childNodeIndex(node)];\n    } else if (cursor.node === node.parentNode && cursor.offset > childNodeIndex(node)) {\n        cursor.offset += nodeSize(node) - 1;\n    }\n}\n\nfunction updateCursorBeforeMergeIntoPreviousSibling(node, cursor) {\n    if (cursor.node === node) {\n        cursor.node = node.previousSibling;\n        cursor.offset += node.previousSibling.childNodes.length;\n    } else if (cursor.node === node.parentNode) {\n        const childIndex = childNodeIndex(node);\n        if (cursor.offset === childIndex) {\n            cursor.node = node.previousSibling;\n            cursor.offset = node.previousSibling.childNodes.length;\n        } else if (cursor.offset > childIndex) {\n            cursor.offset--;\n        }\n    }\n}\n\n/** @typedef {import(\"@html_editor/core/selection_plugin\").Cursor} Cursor */\n\nexport const callbacksForCursorUpdate = {\n    /** @type {(node: Node) => (cursor: Cursor) => void} */\n    remove: (node) => (cursor) => updateCursorBeforeRemove(node, cursor),\n    /** @type {(ref: HTMLElement, node: Node) => (cursor: Cursor) => void} */\n    before: (ref, node) => (cursor) =>\n        updateCursorBeforeMove(ref.parentNode, childNodeIndex(ref), node, cursor),\n    /** @type {(ref: HTMLElement, node: Node) => (cursor: Cursor) => void} */\n    after: (ref, node) => (cursor) =>\n        updateCursorBeforeMove(ref.parentNode, childNodeIndex(ref) + 1, node, cursor),\n    /** @type {(ref: HTMLElement, node: Node) => (cursor: Cursor) => void} */\n    append: (to, node) => (cursor) =>\n        updateCursorBeforeMove(to, to.childNodes.length, node, cursor),\n    /** @type {(ref: HTMLElement, node: Node) => (cursor: Cursor) => void} */\n    prepend: (to, node) => (cursor) => updateCursorBeforeMove(to, 0, node, cursor),\n    /** @type {(node: HTMLElement) => (cursor: Cursor) => void} */\n    unwrap: (node) => (cursor) => updateCursorBeforeUnwrap(node, cursor),\n    /** @type {(node: HTMLElement) => (cursor: Cursor) => void} */\n    merge: (node) => (cursor) => updateCursorBeforeMergeIntoPreviousSibling(node, cursor),\n};\n\n/**\n * @param {Selection} selection\n * @param {\"previous\"|\"next\"} side\n * @param {HTMLElement} editable\n * @returns {string | undefined}\n */\nexport function getAdjacentCharacter(selection, side, editable) {\n    let { focusNode, focusOffset } = selection;\n    [focusNode, focusOffset] = getDeepestPosition(focusNode, focusOffset);\n    const originalBlock = closestBlock(focusNode);\n    let adjacentCharacter;\n    while (!adjacentCharacter && focusNode) {\n        if (side === \"previous\") {\n            adjacentCharacter = focusOffset > 0 && focusNode.textContent[focusOffset - 1];\n        } else {\n            adjacentCharacter = focusNode.textContent[focusOffset];\n        }\n        if (!adjacentCharacter) {\n            if (side === \"previous\") {\n                focusNode = previousLeaf(focusNode, editable);\n                focusOffset = focusNode && nodeSize(focusNode);\n            } else {\n                focusNode = nextLeaf(focusNode, editable);\n                focusOffset = 0;\n            }\n            const characterIndex = side === \"previous\" ? focusOffset - 1 : focusOffset;\n            adjacentCharacter = focusNode && focusNode.textContent[characterIndex];\n        }\n    }\n    if (!focusNode || !isContentEditable(focusNode) || closestBlock(focusNode) !== originalBlock) {\n        return undefined;\n    }\n    return adjacentCharacter;\n}\n", "import { closestElement } from \"./dom_traversal\";\n\n/**\n * Get the index of the given table row/cell.\n *\n * @private\n * @param {HTMLTableRowElement|HTMLTableCellElement} trOrTd\n * @returns {number}\n */\nexport function getRowIndex(trOrTd) {\n    const tr = closestElement(trOrTd, \"tr\");\n    return tr.rowIndex;\n}\n\n/**\n * Get the index of the given table cell.\n *\n * @private\n * @param {HTMLTableCellElement} td\n * @returns {number}\n */\nexport function getColumnIndex(td) {\n    return td.cellIndex;\n}\n\n/**\n * Get all the cells of given table\n * (excluding nested table cells).\n *\n * @param {HTMLTableElement} table\n * @returns {Array<HTMLTableCellElement>}\n */\nexport function getTableCells(table) {\n    return [...table.querySelectorAll(\"td, th\")].filter(\n        (cell) => closestElement(cell, \"table\") === table\n    );\n}\n", "/**\n * Creates a function to track whether a key has been seen before.\n * The returned function returns true for the first occurrence of each key,\n * false for subsequent ones.\n */\nexport function trackOccurrences() {\n    const visited = new Set();\n    return function isFirstOccurrence(key) {\n        if (visited.has(key)) {\n            return false;\n        }\n        visited.add(key);\n        return true;\n    };\n}\n\n/**\n * Creates a function to track whether a pair of keys has been seen before.\n * The returned function returns true for the first occurrence of each pair of\n * keys, false for subsequent ones.\n * Order matters, i.e. (a, b) is not the same as (b, a).\n */\nexport function trackOccurrencesPair() {\n    const visited = new Map();\n    /** @type {(a, b) => boolean} */\n    return function isFirstOccurrence(a, b) {\n        if (!visited.has(a)) {\n            visited.set(a, trackOccurrences());\n        }\n        return visited.get(a)(b);\n    };\n}\n", "import { session } from \"@web/session\";\n\nconst ODOO_DOMAIN_REGEX = new RegExp(`^https?://${session.db}\\\\.odoo\\\\.com(/.*)?$`);\n\n/**\n * Checks if the given URL contains the specified hostname and returns a reconstructed URL if it does.\n *\n * @param {string} url - The URL to be checked\n * @param {Array} hostname - The hostname to be included in the modified URL\n * @return {string|boolean} The modified URL with the specified hostname included, or false if the URL does not meet the conditions\n */\nexport function checkURL(url, hostnameList) {\n    if (url) {\n        let potentialURL;\n        try {\n            potentialURL = new URL(url);\n        } catch {\n            return false;\n        }\n        if (hostnameList.includes(potentialURL.hostname)) {\n            return `https://${potentialURL.hostname}${potentialURL.pathname}`;\n        }\n    }\n    return false;\n}\n\n/**\n * @param {string} url\n */\nexport function isImageUrl(url) {\n    const urlFileExtention = url.split(\".\").pop();\n    return [\"jpg\", \"jpeg\", \"png\", \"gif\", \"svg\", \"webp\"].includes(urlFileExtention.toLowerCase());\n}\n\n/**\n * @param {string} platform\n * @param {string} videoId\n * @param {Object} params\n * @throws {Error} if the given video config is not recognized\n * @returns {URL}\n */\nexport function getVideoUrl(platform, videoId, params) {\n    let url;\n    switch (platform) {\n        case \"youtube\":\n            url = new URL(`https://www.youtube.com/embed/${videoId}`);\n            break;\n        case \"vimeo\":\n            url = new URL(`https://player.vimeo.com/video/${videoId}`);\n            break;\n        case \"dailymotion\":\n            url = new URL(`https://www.dailymotion.com/embed/video/${videoId}`);\n            break;\n        case \"instagram\":\n            url = new URL(`https://www.instagram.com/p/${videoId}/embed`);\n            break;\n        default:\n            throw new Error(`Unsupported platform: ${platform}`);\n    }\n    url.search = new URLSearchParams(params);\n    return url;\n}\n\n/**\n * Checks if the given URL is using the domain where the content being\n * edited is reachable, i.e. if this URL should be stripped of its domain\n * part and converted to a relative URL if put as a link in the content.\n *\n * @param {string} url\n * @returns {boolean}\n */\nexport function isAbsoluteURLInCurrentDomain(url, env = null) {\n    // First check if it is a relative URL: if it is, we don't want to check\n    // further as we will always leave those untouched.\n    let hasProtocol;\n    try {\n        hasProtocol = !!new URL(url).protocol;\n    } catch {\n        hasProtocol = false;\n    }\n    if (!hasProtocol) {\n        return false;\n    }\n\n    const urlObj = new URL(url, window.location.origin);\n    return (\n        urlObj.origin === window.location.origin ||\n        // Chosen heuristic to detect someone trying to enter a link using\n        // its Odoo instance domain. We just suppose it should be a relative\n        // URL (if unexpected behavior, the user can just not enter its Odoo\n        // instance domain but its real domain, or opt-out from the domain\n        // stripping). Mentioning an .odoo.com domain, especially its own\n        // one, is always a bad practice anyway.\n        ODOO_DOMAIN_REGEX.test(urlObj.origin)\n    );\n}\n", "import { TableOfContentManager } from \"@html_editor/others/embedded_components/core/table_of_content/table_of_content_manager\";\nimport { Component, onMounted, onWillDestroy, useSubEnv, xml } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { PUBLIC_EMBEDDINGS } from \"@html_editor/public/embedding_sets\";\n\nclass EmbeddedDummy extends Component {\n    static template = xml``;\n    static props = [\"*\"];\n}\n\nexport const getEmbeddingMap = memoize(\n    (embeddings) => new Map(embeddings.map((embedding) => [embedding.name, embedding]))\n);\n\nconst getTocManager = memoize((element) => new TableOfContentManager({ el: element }));\n\n/**\n * Mount EmbeddedComponent in the Knowledge public view.\n */\nexport class EmbeddedComponentInteraction extends Interaction {\n    static selector = \"[data-embedded]\";\n\n    dynamicContent = {\n        _root: {\n            \"t-component\": () => {\n                const embedding = this.getEmbedding(this.el.dataset.embedded) ?? {\n                    Component: EmbeddedDummy,\n                };\n                return this.getComponentInfo(embedding);\n            },\n        },\n    };\n\n    getComponentInfo({ Component: ComponentClass, getEditableDescendants, getProps, name }) {\n        if (ComponentClass === EmbeddedDummy) {\n            return [ComponentClass, {}];\n        }\n        const host = this.el;\n        const interactionsService = this.services[\"public.interactions\"];\n        ComponentClass = class extends ComponentClass {\n            setup() {\n                useSubEnv(subEnv);\n                super.setup();\n                onMounted(() => {\n                    for (const node of [...host.childNodes]) {\n                        // Ensure that only OWL renderings are kept inside\n                        // the host when the component is alive.\n                        if (node.nodeName !== \"OWL-ROOT\") {\n                            if (node.nodeType === Node.ELEMENT_NODE) {\n                                interactionsService.stopInteractions(node);\n                            }\n                            node.remove();\n                        }\n                    }\n                });\n                onWillDestroy(() => {\n                    // Ensure that editableDescendants are kept inside the\n                    // host in case the component should be mounted again later.\n                    const editableDescendants = getEditableDescendants?.(host) ?? {};\n                    host.append(...Object.values(editableDescendants));\n                });\n            }\n        };\n        const subEnv = {};\n        if (getEditableDescendants) {\n            subEnv.getEditableDescendants = getEditableDescendants;\n        }\n        const props = {\n            ...(getProps?.(host) || {}),\n        };\n        this.setupNewComponent({ name: name, env: subEnv, props });\n        return [ComponentClass, props];\n    }\n\n    getEmbedding(name) {\n        return getEmbeddingMap(PUBLIC_EMBEDDINGS).get(name);\n    }\n\n    setupNewComponent({ name, env, props }) {\n        if (name === \"tableOfContent\") {\n            Object.assign(props, {\n                // Define the TOC scope to its siblings.\n                manager: getTocManager(this.el.parentElement),\n            });\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"html_editor.embedded_component\", EmbeddedComponentInteraction);\n", "import { readonlySyntaxHighlightingEmbedding } from \"@html_editor/others/embedded_components/core/syntax_highlighting/readonly_syntax_highlighting\";\nimport { readonlyFileEmbedding } from \"@html_editor/others/embedded_components/core/file/readonly_file\";\nimport { readonlyTableOfContentEmbedding } from \"@html_editor/others/embedded_components/core/table_of_content/table_of_content\";\nimport { toggleBlockEmbedding } from \"@html_editor/others/embedded_components/core/toggle_block/toggle_block\";\nimport { readonlyVideoEmbedding } from \"@html_editor/others/embedded_components/core/video/readonly_video\";\n\nexport const PUBLIC_EMBEDDINGS = [\n    readonlyFileEmbedding,\n    readonlyTableOfContentEmbedding,\n    toggleBlockEmbedding,\n    readonlyVideoEmbedding,\n    readonlySyntaxHighlightingEmbedding,\n];\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { VERSION_SELECTOR } from \"@html_editor/html_migrations/html_migrations_utils\";\nimport { HtmlUpgradeManager } from \"@html_editor/html_migrations/html_upgrade_manager\";\nimport { markup } from \"@odoo/owl\";\n\nconst upgradeElementToInteractionMap = new Map();\n\nexport class HtmlMigrationsInteraction extends Interaction {\n    static selector = VERSION_SELECTOR;\n\n    setup() {\n        const parentElement = this.el.parentElement;\n        if (!parentElement) {\n            this.isComplete = true;\n            return;\n        }\n        for (const el of [...upgradeElementToInteractionMap.keys()]) {\n            if (el.contains(parentElement)) {\n                // Avoid handling an upgrade that is already being handled.\n                this.isComplete = true;\n                return;\n            } else if (parentElement.contains(el)) {\n                // Avoid handling a upgrade with a contained scope.\n                const interaction = upgradeElementToInteractionMap.get(el);\n                interaction.isComplete = true;\n                interaction.container = undefined;\n                upgradeElementToInteractionMap.delete(el);\n            }\n        }\n        this.container = parentElement;\n        upgradeElementToInteractionMap.set(this.container, this);\n    }\n\n    start() {\n        if (this.isComplete || this.isUpgrading || !this.container.isConnected) {\n            // Ensure that an upgrade can only be attempted once, even if\n            // interactions are restarted.\n            return;\n        }\n        this.isUpgrading = true;\n        this.services[\"public.interactions\"].stopInteractions(this.container);\n        const htmlUpgradeManager = new HtmlUpgradeManager();\n        const initialValue = markup(this.container.innerHTML);\n        const upgradedValue = htmlUpgradeManager.processForUpgrade(initialValue);\n        if (initialValue !== upgradedValue) {\n            this.container.innerHTML = upgradedValue;\n        }\n        for (const el of this.container.querySelectorAll(VERSION_SELECTOR)) {\n            delete el.dataset.oeVersion;\n        }\n        this.services[\"public.interactions\"].startInteractions(this.container);\n        this.isUpgrading = false;\n        this.isComplete = true;\n    }\n\n    destroy() {\n        if (this.isComplete && this.container) {\n            // Ensure that the container reference is kept during the upgrade\n            // so that no other upgrade can start in the same Element while this\n            // one is still ongoing.\n            upgradeElementToInteractionMap.delete(this.container);\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"html_editor.html_migrations\", HtmlMigrationsInteraction);\n", "import { htmlEscape, markup } from \"@odoo/owl\";\n\nimport { router } from \"@web/core/browser/router\";\nimport { loadEmoji, loader } from \"@web/core/emoji_picker/emoji_picker\";\nimport { normalize } from \"@web/core/l10n/utils\";\nimport {\n    createDocumentFragmentFromContent,\n    createElementWithContent,\n    htmlFormatList,\n    htmlJoin,\n    htmlReplace,\n    htmlReplaceAll,\n    htmlTrim,\n    setElementContent,\n} from \"@web/core/utils/html\";\nimport { escapeRegExp } from \"@web/core/utils/strings\";\nimport { getOrigin } from \"@web/core/utils/urls\";\nimport { setAttributes } from \"@web/core/utils/xml\";\n\nconst urlRegexp =\n    /\\b(?:https?:\\/\\/\\d{1,3}(?:\\.\\d{1,3}){3}|(?:https?:\\/\\/|(?:www\\.))[-a-z0-9@:%._+~#=\\u00C0-\\u024F\\u1E00-\\u1EFF]{1,256}\\.[a-z]{2,13})\\b(?:[-a-z0-9@:%_+~#?&[\\]^|{}`\\\\'$//=\\u00C0-\\u024F\\u1E00-\\u1EFF]|[.]*[-a-z0-9@:%_+~#?&[\\]^|{}`\\\\'$//=\\u00C0-\\u024F\\u1E00-\\u1EFF]|,(?!$| )|\\.(?!$| |\\.)|;(?!$| ))*/gi;\nconst messageUrlRegExp = new RegExp(`^${escapeRegExp(getOrigin())}/mail/message/(\\\\d+)$`);\n\n/**\n * @param {string|ReturnType<markup>} rawBody\n * @param {Object} validMentions\n * @param {import(\"models\").Persona[]} validMentions.partners\n * @returns {Promise<string|ReturnType<markup>>}\n */\nexport function prettifyMessageText(rawBody, { validMentions = {}, thread } = {}) {\n    if (rawBody instanceof markup().constructor) {\n        // markup is already \"pretty\"\n        return rawBody;\n    }\n    let body = htmlTrim(rawBody);\n    body = htmlReplace(body, /(\\r|\\n){2,}/g, () => markup`<br/><br/>`);\n    body = htmlReplace(body, /(\\r|\\n)/g, () => markup`<br/>`);\n    body = htmlReplace(body, /&nbsp;/g, () => \" \");\n    body = htmlTrim(body);\n    // This message will be received from the mail composer as html content\n    // subtype but the urls will not be linkified. If the mail composer\n    // takes the responsibility to linkify the urls we end up with double\n    // linkification a bit everywhere. Ideally we want to keep the content\n    // as text internally and only make html enrichment at display time but\n    // the current design makes this quite hard to do.\n    body = generateMentionsLinks(body, { ...validMentions, thread });\n    body = parseAndTransform(body, addLink);\n    return body;\n}\n\n/**\n * @param {string|ReturnType<markup>} htmlBody\n */\nexport async function generateEmojisOnHtml(htmlBody, { allowEmojiLoading = true } = {}) {\n    let body = htmlBody;\n    if (allowEmojiLoading || odoo.loader.modules.get(\"@web/core/emoji_picker/emoji_data\")) {\n        body = await _generateEmojisOnHtml(body);\n    }\n    return body;\n}\n\n/**\n * @param {string|ReturnType<markup>} rawBody\n * @param {Object} validMentions\n * @param {import(\"models\").Persona[]} validMentions.partners\n */\nexport async function prettifyMessageContent(\n    rawBody,\n    { validMentions = [], allowEmojiLoading = true } = {}\n) {\n    let body = prettifyMessageText(rawBody, { validMentions });\n    body = await generateEmojisOnHtml(body, { allowEmojiLoading });\n    return body;\n}\n\n/**\n * WARNING: this is not enough to unescape potential XSS contained in htmlString, transformFunction\n * should handle it or it should be handled after/before calling parseAndTransform. So if the result\n * of this function is used in a t-raw, be very careful.\n *\n * @param {string|ReturnType<markup>} htmlString\n * @param {function} transformFunction\n * @returns {ReturnType<markup>}\n */\nexport function parseAndTransform(htmlString, transformFunction) {\n    const div = document.createElement(\"div\");\n    try {\n        setElementContent(div, htmlString);\n    } catch {\n        div.appendChild(createElementWithContent(\"pre\", htmlString));\n    }\n    return _parseAndTransform(Array.from(div.childNodes), transformFunction);\n}\n\n/**\n * @param {Node[]} nodes\n * @param {function} transformFunction with:\n *   param node\n *   param function\n *   return string\n * @return {ReturnType<markup>}\n */\nfunction _parseAndTransform(nodes, transformFunction) {\n    if (!nodes) {\n        return;\n    }\n    return htmlJoin(\n        Object.values(nodes).map((node) =>\n            transformFunction(node, function () {\n                return _parseAndTransform(node.childNodes, transformFunction);\n            })\n        )\n    );\n}\n\n/**\n * @param {string} text\n * @return {ReturnType<markup>} linkified text\n */\nfunction linkify(text) {\n    let curIndex = 0;\n    let result = \"\";\n    let match;\n    while ((match = urlRegexp.exec(text)) !== null) {\n        result = htmlJoin([result, text.slice(curIndex, match.index)]);\n        // Decode the url first, in case it's already an encoded url\n        const inputUrl = decodeURI(match[0]);\n        const url = !/^https?:\\/\\//i.test(inputUrl) ? \"http://\" + inputUrl : inputUrl;\n        const link = document.createElement(\"a\");\n        setAttributes(link, {\n            target: \"_blank\",\n            rel: \"noreferrer noopener\",\n            href: encodeURI(url),\n        });\n        link.textContent = inputUrl;\n        const messageMatch = messageUrlRegExp.exec(url);\n        if (messageMatch !== null) {\n            setAttributes(link, {\n                \"data-oe-id\": messageMatch[1],\n                \"data-oe-model\": \"mail.message\",\n            });\n            link.classList.add(\"o_message_redirect\");\n        }\n        // markup: outerHTML is safe when used as a node\n        result = htmlJoin([result, markup(link.outerHTML)]);\n        curIndex = match.index + match[0].length;\n    }\n    return htmlJoin([result, text.slice(curIndex)]);\n}\n\n/**\n * @param {Node} node\n * @param {function} transformFunction\n * @return {ReturnType<markup>}\n */\nexport function addLink(node, transformChildren) {\n    if (node.nodeType === 3) {\n        // text node\n        const linkified = linkify(node.textContent);\n        if (linkified.toString() !== node.textContent) {\n            const div = createElementWithContent(\"div\", linkified);\n            for (const childNode of [...div.childNodes]) {\n                node.parentNode.insertBefore(childNode, node);\n            }\n            node.parentNode.removeChild(node);\n            return linkified;\n        }\n        return node.textContent;\n    }\n    if (node.tagName === \"A\") {\n        return markup(node.outerHTML);\n    }\n    transformChildren();\n    return markup(node.outerHTML);\n}\n\nfunction generateMentionElement({ className, id, model, text }) {\n    const link = document.createElement(\"a\");\n    setAttributes(link, {\n        href: router.stateToUrl({ model: model, resId: id }),\n        class: className,\n        \"data-oe-id\": id,\n        \"data-oe-model\": model,\n        \"data-oe-protected\": \"true\",\n        target: \"_blank\",\n        contenteditable: \"false\",\n    });\n    link.textContent = text;\n    return link;\n}\n\n/**\n * @param {import(\"models\").ResPartner} partner\n * @param {import(\"models\").Thread} thread\n */\nexport function generatePartnerMentionElement(partner, thread) {\n    return generateMentionElement({\n        className: \"o_mail_redirect\",\n        id: partner.id,\n        model: \"res.partner\",\n        text: `@${thread?.getPersonaName(partner) ?? partner.name}`,\n    });\n}\n\n/** @param {import(\"models\").ResRole} role */\nexport function generateRoleMentionElement(role) {\n    return generateMentionElement({\n        className: \"o-discuss-mention\",\n        id: role.id,\n        model: \"res.role\",\n        text: `@${role.name}`,\n    });\n}\n\n/** @param {string} label */\nexport function generateSpecialMentionElement(label) {\n    const link = document.createElement(\"a\");\n    setAttributes(link, {\n        class: \"o-discuss-mention\",\n        \"data-oe-protected\": \"true\",\n        contenteditable: \"false\",\n    });\n    link.textContent = `@${label}`;\n    return link;\n}\n\n/** @param {import(\"models\").Thread} thread */\nexport function generateThreadMentionElement(thread) {\n    return generateMentionElement({\n        className: `o_channel_redirect${\n            thread.parent_channel_id ? \" o_channel_redirect_asThread\" : \"\"\n        }`,\n        id: thread.id,\n        model: \"discuss.channel\",\n        text: `#${thread.fullNameWithParent}`,\n    });\n}\n\n/**\n * @param {string|ReturnType<markup>} body\n * @param {Object} param1\n * @param {import(\"models\").ResPartner[]} param1.partners\n * @param {import(\"models\").ResRole[]} param1.roles\n * @param {import(\"models\").Thread[]} param1.threads\n * @param {string[]} param1.specialMentions\n * @param {import(\"models\").Thread} param1.thread\n * @return {ReturnType<markup>}\n */\nfunction generateMentionsLinks(\n    body,\n    { partners = [], roles = [], threads = [], specialMentions = [], thread }\n) {\n    const mentions = [];\n    for (const partner of partners) {\n        const placeholder = `@-mention-partner-${partner.id}`;\n        const text = `@${thread?.getPersonaName(partner) ?? partner.name}`;\n        mentions.push({\n            link: generatePartnerMentionElement(partner, thread),\n            placeholder,\n        });\n        body = htmlReplace(body, text, placeholder);\n    }\n    for (const thread of threads) {\n        const placeholder = `#-mention-channel-${thread.id}`;\n        const text = `#${thread.fullNameWithParent}`;\n        mentions.push({\n            link: generateThreadMentionElement(thread),\n            placeholder,\n        });\n        body = htmlReplace(body, text, placeholder);\n    }\n    for (const special of specialMentions) {\n        const text = `@${special}`;\n        const placeholder = `@-mention-special-${special}`;\n        mentions.push({\n            link: generateSpecialMentionElement(special),\n            placeholder,\n        });\n        body = htmlReplace(body, text, placeholder);\n    }\n    for (const role of roles) {\n        const placeholder = `@-mention-role-${role.id}`;\n        const text = `@${role.name}`;\n        mentions.push({\n            link: generateRoleMentionElement(role),\n            placeholder,\n        });\n        body = htmlReplace(body, text, placeholder);\n    }\n    for (const mention of mentions) {\n        const link = mention.link;\n        // markup: outerHTML is safe when used as a node\n        body = htmlReplace(body, mention.placeholder, markup(link.outerHTML));\n    }\n    return htmlEscape(body);\n}\n\n/**\n * @private\n * @param {string|ReturnType<markup>} htmlString\n * @returns {Promise<ReturnType<markup>>}\n */\nasync function _generateEmojisOnHtml(htmlString) {\n    const { emojis } = await loadEmoji();\n    for (const emoji of emojis) {\n        for (const source of [...emoji.shortcodes, ...emoji.emoticons]) {\n            const escapedSource = htmlEscape(String(source));\n            const regexp = new RegExp(\n                \"(\\\\s|^)(\" + escapeRegExp(escapedSource) + \")(?=\\\\s|$|<)\",\n                \"g\"\n            );\n            htmlString = htmlReplace(htmlString, regexp, (_, group1) => group1 + emoji.codepoints);\n        }\n    }\n    return htmlEscape(htmlString);\n}\n\n/**\n * @param {string|ReturnType<markup>} body\n * @returns {ReturnType<markup>}\n */\nexport function getNonEditableMentions(body) {\n    const doc = createDocumentFragmentFromContent(body);\n    for (const block of doc.body.querySelectorAll(\".o_mail_reply_hide\")) {\n        block.classList.remove(\"o_mail_reply_hide\");\n    }\n    // for mentioned partner\n    for (const mention of doc.body.querySelectorAll(\".o_mail_redirect\")) {\n        mention.setAttribute(\"contenteditable\", false);\n    }\n    // for mentioned channel\n    for (const mention of doc.body.querySelectorAll(\".o_channel_redirect\")) {\n        mention.setAttribute(\"contenteditable\", false);\n    }\n    // for special mentions\n    for (const mention of doc.body.querySelectorAll(\".o-discuss-mention\")) {\n        mention.setAttribute(\"contenteditable\", false);\n    }\n    return markup(doc.body.innerHTML);\n}\n\n/**\n * @param {string|ReturnType<markup>} htmlString\n * @returns {string}\n */\nexport function htmlToTextContentInline(htmlString) {\n    htmlString = htmlReplace(htmlString, /<br\\s*\\/?>/gi, () => \" \");\n    const div = document.createElement(\"div\");\n    try {\n        setElementContent(div, htmlString);\n    } catch {\n        div.appendChild(createElementWithContent(\"pre\", htmlString));\n    }\n    return div.textContent\n        .trim()\n        .replace(/[\\n\\r]/g, \"\")\n        .replace(/\\s\\s+/g, \" \");\n}\n\nexport function convertBrToLineBreak(str) {\n    str = htmlReplace(str, /<br\\s*\\/?>/gi, () => \"\\n\");\n    return createDocumentFragmentFromContent(str).body.textContent;\n}\n\nexport function cleanTerm(term) {\n    return typeof term === \"string\" ? normalize(term) : \"\";\n}\n\n/**\n * Parses text to find email: Tagada <address@mail.fr> -> [Tagada, address@mail.fr] or False\n *\n * @param {string} text\n * @returns {[string,string|boolean]|false}\n */\nexport function parseEmail(text) {\n    if (!text) {\n        return;\n    }\n    let result = text.match(/\"?(.*?)\"? <(.*@.*)>/);\n    if (result) {\n        const name = (result[1] || \"\").trim().replace(/(^\"|\"$)/g, \"\");\n        return [name, (result[2] || \"\").trim()];\n    }\n    result = text.match(/(.*@.*)/);\n    if (result) {\n        return [String(result[1] || \"\").trim(), String(result[1] || \"\").trim()];\n    }\n    return [text, false];\n}\n\nexport const EMOJI_REGEX = /\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F|\\u200d/gu;\n\n/**\n * Wrap emojis present in the given text with a title and return a safe HTML\n * string.\n *\n * @param {string|ReturnType<markup>} content\n * @returns {ReturnType<markup>}\n */\nexport function decorateEmojis(content) {\n    if (!loader.loaded || !content) {\n        return content;\n    }\n    const doc = createDocumentFragmentFromContent(content);\n    const nodes = doc.evaluate(\n        \".//text()\",\n        doc.body,\n        null,\n        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,\n        null\n    );\n    for (let i = 0; i < nodes.snapshotLength; i++) {\n        const node = nodes.snapshotItem(i);\n        const span = document.createElement(\"span\");\n        setElementContent(\n            span,\n            htmlReplaceAll(node.textContent, loader.loaded.emojiRegex, (codepoints) =>\n                markup(\n                    `<span class=\"o-mail-emoji\" title=\"${htmlFormatList(\n                        loader.loaded.emojiValueToShortcodes[codepoints],\n                        { style: \"unit-narrow\" }\n                    )}\">${htmlEscape(codepoints)}</span>`\n                )\n            )\n        );\n        node.replaceWith(...span.childNodes);\n    }\n    return markup(doc.body.innerHTML);\n}\n\n/**\n * Converts an object of key/value to string, where object represents a attClass with OWL syntax object\n * and value is evaluation of each key.\n * Example: \"attClassObjectToString({ a: 1, b: 0, c: 1 })\" converts to \"a c\".\n */\nexport function attClassObjectToString(obj) {\n    return Object.entries(obj)\n        .filter(([_, val]) => val)\n        .map(([key, _]) => key)\n        .join(\" \");\n}\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { addLoadingEffect } from \"@web/core/utils/ui\";\n\nexport class Signup extends Interaction {\n    static selector = \".oe_signup_form, .oe_reset_password_form\";\n    dynamicContent = {\n        _root: { \"t-on-submit\": this.onSubmit },\n    };\n\n    onSubmit() {\n        const submitEl = this.el.querySelector('.oe_login_buttons > button[type=\"submit\"]');\n        if (submitEl && !submitEl.disabled) {\n            const removeLoadingEffect = addLoadingEffect(submitEl);\n            this.registerCleanup(removeLoadingEffect);\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"auth_signup.signup\", Signup);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { rpc } from '@web/core/network/rpc';\nimport { redirect } from '@web/core/utils/urls';\n\nexport class CustomerAddress extends Interaction {\n    // /my/address & /my/account\n    static selector = '.o_customer_address_fill';\n    dynamicContent = {\n        'select[name=\"country_id\"]': { 't-on-change': this.debounced(this.onChangeCountry, 500) },\n        'select[name=\"state_id\"]': { 't-on-change': this.onChangeState },\n        '#save_address': { 't-on-click.prevent': this.locked(this.saveAddress, true) },\n    };\n\n    setup() {\n        this.http = this.services['http'];\n        this.addressForm = this.el.querySelector('form.address_autoformat');\n        this.errorsDiv = this.el.querySelector('#errors');\n        this.addressType = this.addressForm['address_type'].value;\n        this.countryCode = this.addressForm.dataset.companyCountryCode;\n        this.requiredFields = this.addressForm.required_fields.value.split(',');\n        this.requiredFields.forEach((fieldName) => this._markRequired(fieldName, true));\n    }\n\n    async willStart() {\n        await this._onChangeCountry(true);\n    }\n\n    async onChangeCountry() {\n        return this._onChangeCountry();\n    }\n\n    /**\n     * Overridable hook.\n     */\n    async onChangeState() {}\n\n    async _onChangeCountry(init=false) {\n        const countryId = parseInt(this.addressForm.country_id.value);\n        if (!countryId) return;\n\n        const data = await this.waitFor(rpc(\n            `/my/address/country_info/${countryId}`,\n            {address_type: this.addressType},\n        ));\n\n        this.addressForm.phone.placeholder = data.phone_code !== 0 ? `+${data.phone_code}` : '';\n\n        // populate states and display\n        const selectStates = this.addressForm.state_id;\n        if (!init || selectStates.options.length === 1) {\n            // dont reload state at first loading (done in qweb)\n            if (data.states.length || data.state_required) {\n                // empty existing options, only keep the placeholder.\n                selectStates.options.length = 1;\n\n                // create new options and append them to the select element\n                data.states.forEach((state) => {\n                    const option = new Option(state[1], state[0]);\n                    // Used by localizations\n                    option.setAttribute('data-code', state[2]);\n                    selectStates.appendChild(option);\n                });\n                this._showInput('state_id');\n            } else {\n                this._hideInput('state_id');\n            }\n        }\n\n        // manage fields order / visibility\n        if (data.fields) {\n            if (data.zip_before_city) {\n                this._getInputDiv('zip').after(this._getInputDiv('city'));\n            } else {\n                this._getInputDiv('zip').before(this._getInputDiv('city'));\n            }\n\n            const all_fields = ['street', 'zip', 'city'];\n            all_fields.forEach((fname) => {\n                if (data.fields.includes(fname)) {\n                    this._showInput(fname);\n                } else {\n                    this._hideInput(fname);\n                }\n            });\n        }\n\n        const required_fields = this.addressForm.querySelectorAll(':required');\n        required_fields.forEach((element) => {\n            // remove requirement on previously required fields\n            if (\n                !data.required_fields.includes(element.name)\n                && !this.requiredFields.includes(element.name)\n            ) {\n                this._markRequired(element.name, false);\n            }\n        });\n        data.required_fields.forEach((fieldName) => {\n            this._markRequired(fieldName, true);\n        })\n    }\n\n    _getInputDiv(name) {\n        return this.addressForm[name].parentElement;\n    }\n\n    _getInputLabel(name) {\n        const input = this.addressForm[name];\n        return input?.parentElement.querySelector(`label[for='${input.id}']`);\n    }\n\n    _showInput(name) {\n        // show parent div, containing label and input\n        this.addressForm[name].parentElement.style.display = '';\n    }\n\n    _hideInput(name) {\n        // show parent div, containing label and input\n        this.addressForm[name].parentElement.style.display = 'none';\n    }\n\n    _markRequired(name, required) {\n        const input = this.addressForm[name];\n        if (input) {\n            input.required = required;\n        }\n        this._getInputLabel(name)?.classList.toggle('label-optional', !required);\n    }\n\n    /**\n     * Disable the button, submit the form and add a spinner while the submission is ongoing.\n     *\n     * @param {Event} ev\n     */\n    async saveAddress(ev) {\n        ev.preventDefault();  // avoid potential redirect if href set on link\n        if (!this.addressForm.reportValidity()) return;\n\n        const result = await this.waitFor(this.http.post(\n            this.addressForm.dataset.submitUrl,\n            new FormData(this.addressForm),\n        ))\n        if (result.redirectUrl) {\n            redirect(result.redirectUrl);\n        } else {\n            // Highlight missing/invalid form values\n            this.el.querySelectorAll('.is-invalid').forEach(element => {\n                if (!result.invalid_fields.includes(element.name)) {\n                    element.classList.remove('is-invalid');\n                }\n            })\n            result.invalid_fields.forEach(\n                fieldName => this.addressForm[fieldName].classList.add('is-invalid')\n            );\n\n            // Display the error messages\n            // NOTE: setCustomValidity is not used as we would have to reset the error msg on\n            // input update, which is not worth catching for the rare cases where the\n            // server-side validation will catch validation issues (now that required inputs\n            // are also handled client-side)\n            const newErrors = result.messages.map(message => {\n                const errorHeader = document.createElement('h5');\n                errorHeader.classList.add('text-danger');\n                errorHeader.appendChild(document.createTextNode(message));\n                return errorHeader;\n            });\n\n            this.errorsDiv.replaceChildren(...newErrors);\n        }\n    }\n\n    /**\n     * Gets the selected country code.\n     *\n     * Used in overrides.\n     */\n    _getSelectedCountryCode() {\n        const country = this.addressForm.country_id;\n        return country.value ? country.selectedOptions[0].getAttribute('code') : '';\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('portal.customer_address', CustomerAddress);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { rpc } from '@web/core/network/rpc';\n\nexport class AddressCard extends Interaction {\n    static selector = '.o_portal_addresses';\n    dynamicContent = {\n        '.o_remove_address': { 't-on-click.prevent': this.removeAddress },\n        '#use_delivery_as_billing': { 't-on-change': this.toggleBillingAddressRow },\n    };\n\n     setup() {\n        this.billingContainer = this.el.querySelector('#billing_container');\n        this.addBillingAddressBtn = this.el.querySelector('.o_add_billing_address_btn');\n    }\n\n    /**\n     * Archive the address.\n     *\n     * @param {Event} ev\n     */\n    async removeAddress(ev) {\n        await this.waitFor(rpc('/my/address/archive', {\n            partner_id: ev.currentTarget.dataset.partnerId,\n        }));\n        location.reload();\n    }\n\n    /**\n     * Show/hide the billing address row when the user toggles the \"use delivery as billing\" input.\n     *\n     * The URLs of the \"create address\" buttons are updated to propagate the value of the input.\n     *\n     * @param {Event} ev\n     */\n    async toggleBillingAddressRow(ev) {\n        const useDeliveryAsBilling = ev.target.checked;\n\n        const addDeliveryAddressButton = this.el.querySelector(\n            '.o_address_card_add_new[data-address-type=\"delivery\"]'\n        );\n        if (addDeliveryAddressButton) {  // If `Add address` button for delivery.\n            // Update the `use_delivery_as_billing` query param for a new delivery address URL.\n            const addDeliveryUrl = new URL(addDeliveryAddressButton.href);\n            addDeliveryUrl.searchParams.set(\n                'use_delivery_as_billing', encodeURIComponent(useDeliveryAsBilling)\n            );\n            addDeliveryAddressButton.href = addDeliveryUrl.toString();\n        }\n\n        // Toggle the billing address row.\n        if (useDeliveryAsBilling) {\n            this.billingContainer.classList.add('d-none');  // Hide the billing address row.\n        } else {\n            this.billingContainer.classList.remove('d-none');  // Show the billing address row.\n        }\n        this.addBillingAddressBtn.classList.toggle('d-none', useDeliveryAsBilling);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('portal.address_card', AddressCard);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { post } from \"@web/core/network/http_service\";\nimport { Component, markup } from \"@odoo/owl\";\nimport { rpc, RPCError } from \"@web/core/network/rpc\";\n\n/**\n * Display the composer (according to access right)\n */\nexport class PortalComposer extends Interaction {\n    static selector = \".o_portal_chatter_composer\";\n    static selectorHas = \".o_portal_chatter_composer_input\";\n    dynamicContent = {\n        \".o_portal_chatter_file_input\": {\n            \"t-on-change\": this.onFileInputChange,\n        },\n        \".o_portal_chatter_attachment_btn\": {\n            \"t-on-click\": this.onAttachmentButtonClick,\n        },\n        \".o_portal_chatter_attachment_delete\": {\n            \"t-on-click.prevent.stop.withTarget\": this.locked(this.onAttachmentDeleteClick, true),\n        },\n        \".o_portal_chatter_composer_btn\": {\n            \"t-on-click.prevent.withTarget\": this.locked(this.onSubmitButtonClick, true),\n        },\n    };\n\n    static prepareOptions(options) {\n        if (typeof options.default_attachment_ids === \"string\") {\n            options.default_attachment_ids = JSON.parse(options.default_attachment_ids);\n        }\n        for (const name of [\"res_id\", \"partner_id\", \"pid\"]) {\n            if (typeof options[name] === \"string\") {\n                options[name] = parseInt(options[name]);\n            }\n        }\n        return Object.assign(\n            {\n                allow_composer: true,\n                display_composer: false,\n                csrf_token: odoo.csrf_token,\n                token: false,\n                res_model: false,\n                res_id: false,\n                send_button_label: _t(\"Send\"),\n            },\n            options || {}\n        );\n    }\n\n    setup() {\n        this.options = this.env.portalComposerOptions || PortalComposer.prepareOptions({});\n        this.attachments = [];\n        this.attachmentButtonEl = this.el.querySelector(\".o_portal_chatter_attachment_btn\");\n        this.fileInputEl = this.el.querySelector(\".o_portal_chatter_file_input\");\n        this.sendButtonEl = this.el.querySelector(\".o_portal_chatter_composer_btn\");\n        this.attachmentsEl = this.el.querySelector(\n            \".o_portal_chatter_composer_input .o_portal_chatter_attachments\"\n        );\n        this.inputTextareaEl = this.el.querySelector(\n            '.o_portal_chatter_composer_input textarea[name=\"message\"]'\n        );\n    }\n\n    start() {\n        if (this.options.default_attachment_ids) {\n            this.attachments = this.options.default_attachment_ids || [];\n            for (const attachment of this.attachments) {\n                attachment.state = \"done\";\n            }\n            this.updateAttachments();\n        }\n    }\n\n    onAttachmentButtonClick() {\n        this.fileInputEl.click();\n    }\n\n    async onAttachmentDeleteClick(ev, currentTargetEl) {\n        const attachmentId = parseInt(\n            currentTargetEl.closest(\".o_portal_chatter_attachment\").dataset.id\n        );\n        const accessToken = this.attachments.find(\n            (attachment) => attachment.id === attachmentId\n        ).access_token;\n\n        this.sendButtonEl.disabled = true;\n\n        await this.waitFor(\n            rpc(\"/portal/attachment/remove\", {\n                attachment_id: attachmentId,\n                access_token: accessToken,\n            })\n        );\n        this.attachments = this.attachments.filter((attachment) => attachment.id !== attachmentId);\n        this.updateAttachments();\n        this.sendButtonEl.disabled = false;\n    }\n\n    prepareAttachmentData(file) {\n        return {\n            is_pending: true,\n            thread_id: this.options.res_id,\n            thread_model: this.options.res_model,\n            token: this.options.token,\n            ufile: file,\n        };\n    }\n\n    async onFileInputChange() {\n        this.sendButtonEl.disabled = true;\n\n        await this.waitFor(\n            Promise.all(\n                [...this.fileInputEl.files].map(\n                    (file) =>\n                        new Promise((resolve, reject) => {\n                            const data = this.prepareAttachmentData(file);\n                            if (odoo.csrf_token) {\n                                data.csrf_token = odoo.csrf_token;\n                            }\n                            this.waitFor(post(\"/mail/attachment/upload\", data))\n                                .then((res) => {\n                                    const attachmentId = res.data[\"attachment_id\"];\n                                    const attachment = res.data[\"store_data\"][\"ir.attachment\"].find(\n                                        (att) => att.id === attachmentId\n                                    );\n                                    attachment.state = \"pending\";\n                                    this.attachments.push(attachment);\n                                    this.updateAttachments();\n                                    resolve();\n                                })\n                                .catch((error) => {\n                                    if (error instanceof RPCError) {\n                                        this.services.notification.add(\n                                            _t(\n                                                \"Could not save file %s\",\n                                                markup`<strong>${file.name}</strong>`\n                                            ),\n                                            { type: \"warning\", sticky: true }\n                                        );\n                                        resolve();\n                                    }\n                                });\n                        })\n                )\n            )\n        );\n        // ensures any selection triggers a change, even if the same files are selected again\n        this.fileInputEl.value = null;\n        this.sendButtonEl.disabled = false;\n    }\n\n    prepareMessageData() {\n        return {\n            thread_model: this.options.res_model,\n            thread_id: this.options.res_id,\n            post_data: {\n                body: this.el.querySelector('textarea[name=\"message\"]').value,\n                attachment_ids: this.attachments.map((a) => a.id),\n                message_type: \"comment\",\n                subtype_xmlid: \"mail.mt_comment\",\n                attachment_tokens: this.attachments.map((a) => a.ownership_token),\n            },\n            token: this.options.token,\n            hash: this.options.hash,\n            pid: this.options.pid,\n        };\n    }\n\n    async onSubmitButtonClick(ev, currentTargetEl) {\n        const error = this.onSubmitCheckContent();\n        if (error) {\n            this.inputTextareaEl.classList.add(\"border-danger\");\n            const errorEl = this.el.querySelector(\".o_portal_chatter_composer_error\");\n            errorEl.innerText = error;\n            errorEl.classList.remove(\"d-none\");\n            return Promise.reject();\n        } else {\n            return this.chatterPostMessage(currentTargetEl.dataset.action);\n        }\n    }\n\n    onSubmitCheckContent() {\n        if (!this.inputTextareaEl.value.trim() && !this.attachments.length) {\n            return _t(\n                \"Some fields are required. Please make sure to write a message or attach a document\"\n            );\n        }\n    }\n\n    updateAttachments() {\n        this.attachmentsEl.replaceChildren();\n        this.renderAt(\n            \"portal.Chatter.Attachments\",\n            {\n                attachments: this.attachments,\n                showDelete: true,\n            },\n            this.attachmentsEl\n        );\n    }\n\n    /**\n     * post message using rpc call and display new message and message count\n     *\n     * @param {String} route\n     * @returns {Promise}\n     */\n    async chatterPostMessage(route) {\n        const result = await this.waitFor(rpc(route, this.prepareMessageData()));\n        const res = result.store_data || result;\n        Component.env.bus.trigger(\"reload_chatter_content\", res);\n        return res;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"portal.portal_composer\", PortalComposer);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class PortalDetails extends Interaction {\n    static selector = \".o_portal_details\";\n    dynamicContent = {\n        \"select[name=country_id]\": {\n            \"t-on-change\": this.adaptAddressForm,\n        },\n    };\n\n    setup() {\n        this.stateEl = this.el.querySelector(\"select[name=state_id]\");\n        this.stateOptionEls = this.el.querySelectorAll(\n            \"select[name=state_id]:not([disabled]):not([disabled=false]) option:not(:first-child)\"\n        );\n        this.adaptAddressForm();\n    }\n\n    adaptAddressForm() {\n        const countryEl = this.el.querySelector(\"select[name=country_id]\");\n        const countryID = countryEl.value || 0;\n        for (const optionEl of this.stateOptionEls) {\n            optionEl.remove();\n        }\n        let nb = 0;\n        for (const el of this.stateOptionEls) {\n            if (el.dataset.country_id === countryID) {\n                el.classList.remove(\"d-none\");\n                this.stateEl.appendChild(el);\n                nb++;\n            }\n        }\n        this.stateEl.classList.remove(\"d-none\");\n        this.stateEl.parentElement.classList[nb >= 1 ? \"remove\" : \"add\"](\"d-none\");\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"portal.portal_details\", PortalDetails);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport { rpc } from \"@web/core/network/rpc\";\n\nexport class PortalHomeCounters extends Interaction {\n    static selector = \".o_portal_my_home\";\n\n    async willStart() {\n        return this.updateCounters();\n    }\n\n    /**\n     * Return a list of counters name linked to a line that we want to keep\n     * regardless of the number of documents present\n     * @returns {Array}\n     */\n    getCountersAlwaysDisplayed() {\n        return [];\n    }\n\n    async updateCounters() {\n        const needed = Object.values(this.el.querySelectorAll(\"[data-placeholder_count]\")).map(\n            (documentsCounterEl) => documentsCounterEl.dataset[\"placeholder_count\"]\n        );\n        const numberRpc = Math.min(Math.ceil(needed.length / 5), 3); // max 3 rpc, up to 5 counters by rpc ideally\n        const counterByRpc = Math.ceil(needed.length / numberRpc);\n        const countersAlwaysDisplayed = this.getCountersAlwaysDisplayed();\n\n        const proms = [...Array(Math.min(numberRpc, needed.length)).keys()].map(async (i) => {\n            const documentsCountersData = await rpc(\"/my/counters\", {\n                counters: needed.slice(i * counterByRpc, (i + 1) * counterByRpc),\n            });\n            Object.keys(documentsCountersData).forEach((counterName) => {\n                const documentsCounterEl = this.el.querySelector(\n                    `[data-placeholder_count='${counterName}']`\n                );\n                documentsCounterEl.textContent = documentsCountersData[counterName];\n                // The element is hidden by default, only show it if its counter is > 0 or if it's in the list of counters always shown\n                if (\n                    documentsCountersData[counterName] !== 0 ||\n                    countersAlwaysDisplayed.includes(counterName)\n                ) {\n                    documentsCounterEl.closest(\".o_portal_index_card\").classList.remove(\"d-none\");\n                }\n            });\n            return documentsCountersData;\n        });\n        return Promise.all(proms).then((results) => {\n            this.el.querySelector(\".o_portal_doc_spinner\").remove();\n        });\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"portal.portal_home_counters\", PortalHomeCounters);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class PortalSearchPanel extends Interaction {\n    static selector = \".o_portal_search_panel\";\n    dynamicContent = {\n        \".dropdown-item\": {\n            \"t-on-click.prevent.withTarget\": this.onDropdownItemClick,\n        },\n        _root: {\n            \"t-on-submit.prevent\": this.search,\n        },\n    };\n\n    setup() {\n        this.adaptSearchLabel(this.el.querySelector(\".dropdown-item.active\"));\n    }\n\n    adaptSearchLabel(elem) {\n        if (!elem) {\n            return;\n        }\n        const labelEl = elem.cloneNode(true);\n        labelEl.querySelector(\"span.nolabel\")?.remove();\n        this.el\n            .querySelector(\"input[name=search]\")\n            ?.setAttribute(\"placeholder\", labelEl.textContent.trim());\n    }\n\n    search() {\n        const search = new URL(window.location).searchParams;\n        search.set(\n            \"search_in\",\n            this.el\n                .querySelector(\".dropdown-item.active\")\n                ?.getAttribute(\"href\")\n                ?.replace(\"#\", \"\") || \"\"\n        );\n        search.set(\"search\", this.el.querySelector(\"input[name=search]\").value);\n        window.location.search = search.toString();\n    }\n\n    onDropdownItemClick(ev, currentTargetEl) {\n        currentTargetEl\n            .closest(\".dropdown-menu\")\n            .querySelector(\".dropdown-item.active\")\n            ?.classList.remove(\"active\");\n        currentTargetEl.classList.add(\"active\");\n\n        this.adaptSearchLabel(currentTargetEl);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"portal.portal_search_panel\", PortalSearchPanel);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport { ConfirmationDialog } from \"@web/core/confirmation_dialog/confirmation_dialog\";\nimport { renderToMarkup } from \"@web/core/utils/render\";\nimport { InputConfirmationDialog } from \"@portal/js/components/input_confirmation_dialog/input_confirmation_dialog\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { user } from \"@web/core/user\";\n\nexport class PortalSecurity extends Interaction {\n    static selector = \".o_portal_security_body\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _modal: () => document.querySelector(\".modal#portal_deactivate_account_modal\"),\n    };\n    dynamicContent = {\n        \".o_portal_new_api_key\": {\n            \"t-on-click.prevent\": this.onNewApiKeyClick,\n        },\n        \".o_portal_remove_api_key\": {\n            \"t-on-click.prevent\": this.onRemoveApiKeyClick,\n        },\n        _modal: {\n            \"t-on-hide.bs.modal.withTarget\": (event, currentTargetEl) => {\n                // Remove the error messages when we close the modal,\n                // so when we re-open it again we get a fresh new form\n                for (const el of currentTargetEl.querySelectorAll(\".alert, .invalid-feedback\")) {\n                    el.remove();\n                }\n                for (const el of currentTargetEl.querySelectorAll(\".is-invalid\")) {\n                    el.classList.remove(\"is-invalid\");\n                }\n            },\n        },\n        // Defining what happens when you click the \"Log out from all devices\"\n        // on the \"/my/security\" page.\n        \"#portal_revoke_all_sessions_popup\": {\n            \"t-on-click\": this.onRevokeAllSessionsClick,\n        },\n    };\n\n    setup() {\n        // Show the \"deactivate your account\" modal if needed\n        const modalEl = document.querySelector(\".modal.show#portal_deactivate_account_modal\");\n        if (modalEl) {\n            modalEl.classList.remove(\"d-block\");\n            window.Modal.getOrCreateInstance(modalEl).show();\n        }\n    }\n\n    async onNewApiKeyClick() {\n        // This call is done just so it asks for the password confirmation before starting displaying the\n        // dialog forms, to mimic the behavior from the backend, in which it asks for the password before\n        // displaying the wizard.\n        // The result of the call is unused. But it's required to call a method with the decorator `@check_identity`\n        // in order to use `handleCheckIdentity`.\n        await this.waitFor(\n            handleCheckIdentity(\n                this.waitFor(this.services.orm.call(\"res.users\", \"api_key_wizard\", [user.userId])),\n                this.services.orm,\n                this.services.dialog\n            )\n        );\n\n        const { duration } = await this.services.field.loadFields(\"res.users.apikeys.description\", {\n            fieldNames: [\"duration\"],\n        });\n\n        this.services.dialog.add(InputConfirmationDialog, {\n            title: _t(\"New API Key\"),\n            body: renderToMarkup(\"portal.keydescription\", {\n                // Remove `'Custom Date'` selection for portal user\n                duration_selection: duration.selection.filter((option) => option[0] !== \"-1\"),\n            }),\n            confirmLabel: _t(\"Confirm\"),\n            confirm: async ({ inputEl }) => {\n                const formData = Object.fromEntries(new FormData(inputEl.closest(\"form\")));\n                const wizardId = await this.services.orm.create(\"res.users.apikeys.description\", [{\n                    name: formData['description'],\n                    duration: formData['duration']\n                }]);\n                const res = await this.waitFor(\n                    handleCheckIdentity(\n                        this.waitFor(\n                            this.services.orm.call(\"res.users.apikeys.description\", \"make_key\", [\n                                wizardId,\n                            ])\n                        ),\n                        this.services.orm,\n                        this.services.dialog\n                    )\n                );\n\n                this.services.dialog.add(\n                    ConfirmationDialog,\n                    {\n                        title: _t(\"API Key Ready\"),\n                        body: renderToMarkup(\"portal.keyshow\", { key: res.context.default_key }),\n                        confirmLabel: _t(\"Close\"),\n                    },\n                    {\n                        onClose: () => {\n                            window.location.reload();\n                        },\n                    }\n                );\n            },\n        });\n    }\n    async onRemoveApiKeyClick(ev) {\n        await this.waitFor(\n            await handleCheckIdentity(\n                this.waitFor(\n                    this.services.orm.call(\"res.users.apikeys\", \"remove\", [parseInt(ev.target.id)])\n                ),\n                this.services.orm,\n                this.services.dialog\n            )\n        );\n        window.location.reload();\n    }\n    async onRevokeAllSessionsClick() {\n        await this.waitFor(\n            handleCheckIdentity(\n                this.waitFor(\n                    this.services.orm.call(\"res.users\", \"action_revoke_all_devices\", [user.userId])\n                ),\n                this.services.orm,\n                this.services.dialog\n            )\n        );\n        window.location.reload();\n        return true;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"portal.portal_security\", PortalSecurity);\n\n/**\n * Wraps an RPC call in a check for the result being an identity check action\n * descriptor. If no such result is found, just returns the wrapped promise's\n * result as-is; otherwise shows an identity check dialog and resumes the call\n * on success.\n *\n * Warning: does not in and of itself trigger an identity check, a promise which\n * never triggers and identity check internally will do nothing of use.\n *\n * @param {Promise} wrapped promise to check for an identity check request\n * @param {Function} ormService bound do the widget\n * @param {Function} dialogService dialog service\n * @returns {Promise} result of the original call\n */\nexport async function handleCheckIdentity(wrapped, ormService, dialogService) {\n    return wrapped.then((r) => {\n        if (\n            !(\n                r &&\n                r.type &&\n                r.type === \"ir.actions.act_window\" &&\n                r.res_model === \"res.users.identitycheck\"\n            )\n        ) {\n            return r;\n        }\n        const checkId = r.res_id;\n        return new Promise((resolve) => {\n            ormService.write(\"res.users.identitycheck\", [checkId], {auth_method: 'password'});\n            dialogService.add(InputConfirmationDialog, {\n                title: _t(\"Security Control\"),\n                body: renderToMarkup(\"portal.identitycheck\"),\n                confirmLabel: _t(\"Confirm Password\"),\n                confirm: async ({ inputEl }) => {\n                    if (!inputEl.reportValidity()) {\n                        inputEl.classList.add(\"is-invalid\");\n                        return false;\n                    }\n                    let result;\n                    try {\n                        result = await ormService.call(\"res.users.identitycheck\", \"run_check\",\n                            [ checkId ],\n                            { 'context': {'password': inputEl.value} },\n                        );\n                    } catch {\n                        inputEl.classList.add(\"is-invalid\");\n                        inputEl.setCustomValidity(_t(\"Check failed\"));\n                        inputEl.reportValidity();\n                        return false;\n                    }\n                    resolve(result);\n                    return true;\n                },\n                cancel: () => {},\n                onInput: ({ inputEl }) => {\n                    inputEl.classList.remove(\"is-invalid\");\n                    inputEl.setCustomValidity(\"\");\n                },\n            });\n        });\n    });\n}\n", "import { Interaction } from \"@web/public/interaction\";\n\nimport { deserializeDateTime } from \"@web/core/l10n/dates\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { uniqueId } from \"@web/core/utils/functions\";\n\nconst { DateTime } = luxon;\n\nexport class Sidebar extends Interaction {\n\n    setup() {\n        this.printContent = undefined;\n        this.spyWatched = undefined;\n        this.authorizedTextTag = [\"em\", \"b\", \"i\", \"u\"];\n    }\n\n    start() {\n        this.setDelayLabel();\n    }\n\n    /**\n     * Set the due/delay information according to the given date\n     * like : <span class=\"o_portal_sidebar_timeago\" t-att-datetime=\"invoice.date_due\"/>\n     */\n    setDelayLabel() {\n        const timeagoEls = this.el.querySelectorAll(\".o_portal_sidebar_timeago\");\n        for (const timeagoEl of timeagoEls) {\n            const dateTime = deserializeDateTime(timeagoEl.getAttribute(\"datetime\")).startOf(\"day\");\n            const today = DateTime.now().startOf(\"day\");\n            const diff = dateTime.diff(today).as(\"days\");\n            if (diff === 0) {\n                timeagoEl.innerText = _t('Due today');\n            } else if (diff > 0) {\n                timeagoEl.innerText = _t('Due in %s days', Math.abs(diff).toFixed());\n            } else {\n                timeagoEl.innerText = _t('%s days overdue', Math.abs(diff).toFixed());\n            }\n        }\n    }\n\n    /**\n     * @param {string} href\n     */\n    printIframeContent(href) {\n        if (!this.printContent) {\n            const iframeEl = document.createElement(\"iframe\");\n            iframeEl.setAttribute(\"id\", \"print_iframe_content\");\n            iframeEl.setAttribute(\"href\", href);\n            iframeEl.style.display = \"none\";\n            this.printContent = iframeEl;\n            this.insert(this.printContent, this.el);\n            this.addListener(this.printContent, \"load\", () => this.printContent.contentWindow.print());\n        } else {\n            this.printContent.contentWindow.print()\n        }\n    }\n\n    /**\n     * Create a unique id and added as a attribute of spyWatched element\n     *\n     * @param {string} prefix\n     * @param {HTMLElement} el\n     */\n    setElementId(prefix, el) {\n        const id = uniqueId(prefix);\n        el.setAttribute(\"id\", id);\n        return id;\n    }\n\n    generateMenu(linkStyle = {}) {\n        let lastLI = false;\n        let lastUL = null;\n        const bsSidenavEl = this.el.querySelector(\".bs-sidenav\");\n        if (!bsSidenavEl) {\n            return;\n        }\n\n        const quoteEls = document.querySelectorAll(\"#quote_content [id^=quote_header_], #quote_content [id^=quote_]\");\n        for (const quoteEl of quoteEls) {\n            quoteEl.removeAttribute(\"id\");\n        }\n        this.spyWatched.removeAttribute(\"id\");\n\n        const quoteHeaderEls = this.spyWatched.querySelectorAll(\"#quote_content h2, #quote_content h3\");\n        for (const quoteHeaderEl of quoteHeaderEls) {\n            let id = null;\n            let text = null;\n            switch (quoteHeaderEl.tagName.toLowerCase()) {\n                case \"h2\": {\n                    id = this.setElementId(\"quote_header_\", quoteHeaderEl);\n                    text = this.extractText(quoteHeaderEl);\n                    if (!text) {\n                        break;\n                    }\n                    const linkEl = document.createElement(\"a\");\n                    linkEl.classList.add(\"nav-link\", \"p-0\");\n                    linkEl.href = `#${id}`;\n                    linkEl.style = Object.assign(linkEl.style, linkStyle);\n                    linkEl.innerText = text;\n\n                    const liEl = document.createElement(\"li\");\n                    liEl.classList.add(\"nav-item\");\n\n                    liEl.appendChild(linkEl);\n                    this.insert(liEl, bsSidenavEl);\n\n                    lastLI = liEl;\n                    lastUL = false;\n                    break;\n                }\n                case \"h3\": {\n                    id = this.setElementId(\"quote_\", quoteHeaderEl);\n                    text = this.extractText(quoteHeaderEl);\n                    if (!text) {\n                        break;\n                    }\n                    if (lastLI) {\n                        if (!lastUL) {\n                            const ulEl = document.createElement(\"ul\");\n                            ulEl.classList.add(\"nav\", \"flex-column\");\n\n                            this.insert(ulEl, lastLI);\n\n                            lastUL = ulEl;\n                        }\n                        const linkEl = document.createElement(\"a\");\n                        linkEl.classList.add(\"nav-link\", \"p-0\");\n                        linkEl.href = `#${id}`;\n                        linkEl.style = Object.assign(linkEl.style, linkStyle);\n                        linkEl.innerText = text;\n\n                        const liEl = document.createElement(\"li\");\n                        liEl.classList.add(\"nav-item\");\n\n                        liEl.appendChild(linkEl);\n                        this.insert(liEl, lastUL);\n                    }\n                    break;\n                }\n            }\n            quoteHeaderEl.setAttribute(\"data-anchor\", true);\n        }\n    }\n\n    /**\n     * @param {HTMLElement} quoteHeaderEl\n     */\n    extractText(quoteHeaderEl) {\n        const rawText = [];\n        for (const el of quoteHeaderEl.childNodes) {\n            const tagName = el.tagName;\n            const text = el.textContent.trim();\n            if (text && (!tagName || this.authorizedTextTag.includes(tagName.toLowerCase()))) {\n                rawText.push(text);\n            }\n        }\n        return rawText.join(\" \");\n    }\n}\n", "import { useEffect } from \"@odoo/owl\";\nimport { ConfirmationDialog } from \"@web/core/confirmation_dialog/confirmation_dialog\";\n\nexport class InputConfirmationDialog extends ConfirmationDialog {\n    static props = {\n        ...ConfirmationDialog.props,\n        onInput: { type: Function, optional: true },\n    };\n    static template = \"portal.InputConfirmationDialog\";\n\n    setup() {\n        super.setup();\n\n        const onInput = () => {\n            if (this.props.onInput) {\n                this.props.onInput({ inputEl: this.inputEl });\n            }\n        };\n        const onKeydown = (ev) => {\n            if (ev.key && ev.key.toLowerCase() === \"enter\") {\n                ev.preventDefault();\n                this._confirm();\n            }\n        };\n        useEffect(\n            (inputEl) => {\n                this.inputEl = inputEl;\n                if (this.inputEl) {\n                    this.inputEl.focus();\n                    this.inputEl.addEventListener(\"keydown\", onKeydown);\n                    this.inputEl.addEventListener(\"input\", onInput);\n                    return () => {\n                        this.inputEl.removeEventListener(\"keydown\", onKeydown);\n                        this.inputEl.removeEventListener(\"input\", onInput);\n                    };\n                }\n            },\n            () => [this.modalRef.el?.querySelector(\"input\")]\n        );\n    }\n\n    _confirm() {\n        this.execButton(() => this.props.confirm({ inputEl: this.inputEl }));\n    }\n}\n", "import { Component, onMounted, useRef, useState } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { addLoadingEffect } from '@web/core/utils/ui';\nimport { rpc } from \"@web/core/network/rpc\";\nimport { registry } from \"@web/core/registry\";\nimport { redirect } from \"@web/core/utils/urls\";\nimport { NameAndSignature } from \"@web/core/signature/name_and_signature\";\n\n/**\n * This Component is a signature request form. It uses\n * @see NameAndSignature for the input fields, adds a submit\n * button, and handles the RPC to save the result.\n */\nexport class SignatureForm extends Component {\n    static template = \"portal.SignatureForm\"\n    static components = { NameAndSignature }\n    static props = [\"*\"];\n\n    setup() {\n        this.rootRef = useRef(\"root\");\n\n        this.csrfToken = odoo.csrf_token;\n        this.state = useState({\n            error: false,\n            success: false,\n        });\n        this.signature = useState({\n            name: this.props.defaultName,\n            getSignatureImage: () => \"\",\n            resetSignature: () => {},\n        });\n        this.nameAndSignatureProps = {\n            signature: this.signature,\n            fontColor: this.props.fontColor || \"black\",\n        };\n        if (this.props.signatureRatio) {\n            this.nameAndSignatureProps.displaySignatureRatio = this.props.signatureRatio;\n        }\n        if (this.props.signatureType) {\n            this.nameAndSignatureProps.signatureType = this.props.signatureType;\n        }\n        if (this.props.mode) {\n            this.nameAndSignatureProps.mode = this.props.mode;\n        }\n\n        // Correctly set up the signature area if it is inside a modal\n        onMounted(() => {\n            const modal_el = this.rootRef.el.closest('.modal');\n            if (modal_el !== null) {\n                modal_el.addEventListener('shown.bs.modal', () => {\n                    this.signature.resetSignature();\n                    this.toggleSignatureFormVisibility();\n                });\n            }\n        });\n    }\n\n    toggleSignatureFormVisibility() {\n        this.rootRef.el.classList.toggle('d-none', document.querySelector('.editor_enable'));\n    }\n\n    get sendLabel() {\n        return this.props.sendLabel || _t(\"Accept & Sign\");\n    }\n\n     /**\n     * Handles click on the submit button.\n     *\n     * This will get the current name and signature and validate them.\n     * If they are valid, they are sent to the server, and the reponse is\n     * handled. If they are invalid, it will display the errors to the user.\n     *\n     * @returns {Promise}\n     */\n    async onClickSubmit() {\n        const button = document.querySelector('.o_portal_sign_submit')\n        const icon = button.removeChild(button.firstChild)\n        const restoreBtnLoading = addLoadingEffect(button);\n\n        const name = this.signature.name;\n        const signature = this.signature.getSignatureImage().split(\",\")[1];\n        const data = await rpc(this.props.callUrl, { name, signature });\n        if (data.force_refresh) {\n            restoreBtnLoading();\n            button.prepend(icon)\n            if (data.redirect_url) {\n                redirect(data.redirect_url);\n            } else {\n                window.location.reload();\n            }\n            // do not resolve if we reload the page\n            return new Promise(() => {});\n        }\n        this.state.error = data.error || false;\n        this.state.success = !data.error && {\n            message: data.message,\n            redirectUrl: data.redirect_url,\n            redirectMessage: data.redirect_message,\n        };\n    }\n}\n\nregistry.category(\"public_components\").add(\"portal.signature_form\", SignatureForm);\n", "import { Deferred } from \"@web/core/utils/concurrency\";\nimport { loadBundle } from \"@web/core/assets\";\nimport { registry } from \"@web/core/registry\";\nimport { memoize } from \"@web/core/utils/functions\";\n\nodoo.portalChatterReady = new Deferred();\n\nconst loader = {\n    loadChatter: memoize(() => loadBundle(\"portal.assets_chatter\")),\n};\nexport const portalChatterBootService = {\n    start() {\n        const chatterEl = document.querySelector(\".o_portal_chatter\");\n        if (chatterEl) {\n            loader.loadChatter();\n        }\n    },\n};\nregistry.category(\"services\").add(\"portal.chatter.boot\", portalChatterBootService);\n", "import { patch } from \"@web/core/utils/patch\";\nimport { PortalHomeCounters } from '@portal/interactions/portal_home_counters';\n\npatch(PortalHomeCounters.prototype, {\n    /**\n     * @override\n     */\n    getCountersAlwaysDisplayed() {\n        return super.getCountersAlwaysDisplayed(...arguments).concat(['invoice_count']);\n    },\n});\n", "import { Sidebar } from \"@portal/interactions/sidebar\";\nimport { registry } from \"@web/core/registry\";\n\nimport { scrollTo } from \"@web/core/utils/scrolling\";\n\nexport class AccountSidebar extends Sidebar {\n    static selector = \".o_portal_invoice_sidebar\";\n    dynamicContent = {\n        _window: { \"t-on-resize\": this.updateIframeSize },\n        \".o_portal_invoice_print\": { \"t-on-click.prevent.withTarget\": this.onInvoicePrintClick },\n    };\n\n    setup() {\n        super.setup();\n        this.invoiceHTMLEl = undefined;\n    }\n\n    start() {\n        super.start();\n        this.invoiceHTMLEl = document.getElementById('invoice_html');\n        const iframeDoc = this.invoiceHTMLEl.contentDocument || this.invoiceHTMLEl.contentWindow.document;\n        if (iframeDoc.readyState === 'complete') {\n            this.updateIframeSize();\n        } else {\n            this.addListener(this.invoiceHTMLEl, \"load\", this.updateIframeSize);\n        }\n    }\n\n    /**\n     * Called when the iframe is loaded or the window is resized on customer portal.\n     * The goal is to expand the iframe height to display the full report without scrollbar.\n     */\n    updateIframeSize() {\n        const wrapwrapEl = this.invoiceHTMLEl.contentDocument.querySelector(\"div#wrapwrap\");\n        // Set it to 0 first to handle the case where scrollHeight is too big for its content.\n        this.invoiceHTMLEl.height = 0;\n        this.invoiceHTMLEl.height = wrapwrapEl.scrollHeight;\n        // scroll to the right place after iframe resize\n        const isAnchor = /^#[\\w-]+$/.test(window.location.hash)\n        if (!isAnchor) {\n            return;\n        }\n        const targetEl = document.querySelector(`${window.location.hash}`);\n        if (!targetEl) {\n            return;\n        }\n        scrollTo(targetEl, { behavior: \"instant\" });\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onInvoicePrintClick(ev, currentTargetEl) {\n        this.printIframeContent(currentTargetEl.getAttribute(\"href\"));\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"account.account_sidebar\", AccountSidebar);\n", "import { rpc } from \"@web/core/network/rpc\";\nimport { registry } from \"@web/core/registry\";\n\nimport { accountTaxHelpers } from \"@account/helpers/account_tax\";\n\nimport { useState, Component } from \"@odoo/owl\";\n\nexport class TestsSharedJsPython extends Component {\n    static template = \"account.TestsSharedJsPython\";\n    static props = {\n        tests: { type: Array, optional: true },\n    };\n\n    setup() {\n        super.setup();\n        this.state = useState({ done: false });\n    }\n\n    processTest(params) {\n        if (params.test === \"taxes_computation\") {\n            let filter_tax_function = null;\n            if (params.excluded_tax_ids && params.excluded_tax_ids.length) {\n                filter_tax_function = (tax) => !params.excluded_tax_ids.includes(tax.id);\n            }\n\n            const kwargs = {\n                product: params.product,\n                product_uom: params.product_uom,\n                precision_rounding: params.precision_rounding,\n                rounding_method: params.rounding_method,\n                filter_tax_function: filter_tax_function,\n            };\n            const results = {\n                results: accountTaxHelpers.get_tax_details(\n                    params.taxes,\n                    params.price_unit,\n                    params.quantity,\n                    kwargs,\n                )\n            };\n            if (params.rounding_method === \"round_globally\") {\n                results.total_excluded_results = accountTaxHelpers.get_tax_details(\n                    params.taxes,\n                    results.results.total_excluded / params.quantity,\n                    params.quantity,\n                    {...kwargs, special_mode: \"total_excluded\"}\n                );\n                results.total_included_results = accountTaxHelpers.get_tax_details(\n                    params.taxes,\n                    results.results.total_included / params.quantity,\n                    params.quantity,\n                    {...kwargs, special_mode: \"total_included\"}\n                );\n            }\n            return results;\n        }\n        if (params.test === \"adapt_price_unit_to_another_taxes\") {\n            return {\n                price_unit: accountTaxHelpers.adapt_price_unit_to_another_taxes(\n                    params.price_unit,\n                    params.product,\n                    params.original_taxes,\n                    params.new_taxes,\n                    { product_uom: params.product_uom}\n                )\n            }\n        }\n        if (params.test === \"tax_totals_summary\") {\n            const document = this.populateDocument(params.document);\n            const taxTotals = accountTaxHelpers.get_tax_totals_summary(\n                document.lines,\n                document.currency,\n                document.company,\n                {cash_rounding: document.cash_rounding}\n            );\n            return {tax_totals: taxTotals, soft_checking: params.soft_checking};\n        }\n        if (params.test === \"global_discount\") {\n            const document = this.populateDocument(params.document);\n            const baseLines = accountTaxHelpers.prepare_global_discount_lines(\n                document.lines,\n                document.company,\n                params.amount_type,\n                params.amount,\n                \"global_discount\",\n            );\n            document.lines.push(...baseLines);\n            accountTaxHelpers.add_tax_details_in_base_lines(document.lines, document.company);\n            accountTaxHelpers.round_base_lines_tax_details(document.lines, document.company);\n            const taxTotals = accountTaxHelpers.get_tax_totals_summary(\n                document.lines,\n                document.currency,\n                document.company,\n                {cash_rounding: document.cash_rounding}\n            );\n            return {tax_totals: taxTotals, soft_checking: params.soft_checking};\n        }\n        if (params.test === \"down_payment\") {\n            const document = this.populateDocument(params.document);\n            const baseLines = accountTaxHelpers.prepare_down_payment_lines(\n                document.lines,\n                document.company,\n                params.amount_type,\n                params.amount,\n                \"down_payment\",\n            );\n            document.lines = baseLines;\n            accountTaxHelpers.add_tax_details_in_base_lines(document.lines, document.company);\n            accountTaxHelpers.round_base_lines_tax_details(document.lines, document.company);\n            const taxTotals = accountTaxHelpers.get_tax_totals_summary(\n                document.lines,\n                document.currency,\n                document.company,\n                {cash_rounding: document.cash_rounding}\n            );\n            return {\n                tax_totals: taxTotals,\n                soft_checking: params.soft_checking,\n                base_lines_tax_details: this.extractBaseLinesDetails(document),\n            };\n        }\n        if (params.test === \"base_lines_tax_details\") {\n            const document = this.populateDocument(params.document);\n            return {\n                base_lines_tax_details: this.extractBaseLinesDetails(document),\n            };\n        }\n    }\n\n    async processTests() {\n        const tests = this.props.tests || [];\n        const results = tests.map(this.processTest.bind(this));\n        await rpc(\"/account/post_tests_shared_js_python\", { results: results });\n        this.state.done = true;\n    }\n\n    populateDocument(document) {\n        const base_lines = document.lines.map(line => accountTaxHelpers.prepare_base_line_for_taxes_computation(null, line));\n        accountTaxHelpers.add_tax_details_in_base_lines(base_lines, document.company);\n        accountTaxHelpers.round_base_lines_tax_details(base_lines, document.company);\n        return {\n            ...document,\n            lines: base_lines,\n        }\n    }\n\n    extractBaseLinesDetails(document) {\n        return document.lines.map(line => ({\n            total_excluded_currency: line.tax_details.total_excluded_currency,\n            total_excluded: line.tax_details.total_excluded,\n            total_included_currency: line.tax_details.total_included_currency,\n            total_included: line.tax_details.total_included,\n            delta_total_excluded_currency: line.tax_details.delta_total_excluded_currency,\n            delta_total_excluded: line.tax_details.delta_total_excluded,\n            manual_total_excluded_currency: line.manual_total_excluded_currency,\n            manual_total_excluded: line.manual_total_excluded,\n            manual_tax_amounts: line.manual_tax_amounts,\n            taxes_data: line.tax_details.taxes_data.map(tax_data => ({\n                tax_id: tax_data.tax.id,\n                tax_amount_currency: tax_data.tax_amount_currency,\n                tax_amount: tax_data.tax_amount,\n                base_amount_currency: tax_data.base_amount_currency,\n                base_amount: tax_data.base_amount,\n            })),\n        }));\n    }\n}\n\nregistry.category(\"public_components\").add(\"account.tests_shared_js_python\", TestsSharedJsPython);\n", "import { floatIsZero, roundPrecision } from \"@web/core/utils/numbers\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport const accountTaxHelpers = {\n    // -------------------------------------------------------------------------\n    // HELPERS IN BOTH PYTHON/JAVASCRIPT (account_tax.js / account_tax.py)\n    // -------------------------------------------------------------------------\n\n    /**\n     * Helper to stringify a grouping key that could contains some records.\n     *\n     * [!] Only added javascript-side.\n     */\n    stringify_grouping_key(grouping_key) {\n        if (!grouping_key || typeof grouping_key !== \"object\") {\n            return grouping_key;\n        }\n\n        if (\"id\" in grouping_key) {\n            return grouping_key.id;\n        }\n\n        const serializable_grouping_key = { ...grouping_key };\n        for (const [key, value] of Object.entries(grouping_key)) {\n            if (value && typeof value === \"object\" && \"id\" in value) {\n                serializable_grouping_key[key] = value.id;\n            }\n        }\n        return JSON.stringify(serializable_grouping_key);\n    },\n\n    // -------------------------------------------------------------------------\n    // PREPARE TAXES COMPUTATION\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    flatten_taxes_and_sort_them(taxes) {\n        function sort_key(taxes) {\n            return taxes.toSorted((t1, t2) => t1.sequence - t2.sequence || t1.id - t2.id);\n        }\n\n        const group_per_tax = {};\n        const sorted_taxes = [];\n        for (const tax of sort_key(taxes)) {\n            if (tax.amount_type === \"group\") {\n                const children = sort_key(tax.children_tax_ids);\n                for (const child of children) {\n                    group_per_tax[child.id] = tax;\n                    sorted_taxes.push(child);\n                }\n            } else {\n                sorted_taxes.push(tax);\n            }\n        }\n        return { sorted_taxes, group_per_tax };\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    batch_for_taxes_computation(taxes, { special_mode = null, filter_tax_function = null } = {}) {\n        let { sorted_taxes, group_per_tax } = this.flatten_taxes_and_sort_them(taxes);\n        if (filter_tax_function) {\n            sorted_taxes = sorted_taxes.filter(filter_tax_function);\n        }\n\n        const results = {\n            batch_per_tax: {},\n            group_per_tax: group_per_tax,\n            sorted_taxes: sorted_taxes,\n        };\n\n        // Group them per batch.\n        let batch = [];\n        let is_base_affected = false;\n        for (const tax of results.sorted_taxes.toReversed()) {\n            if (batch.length > 0) {\n                const same_batch =\n                    tax.amount_type === batch[0].amount_type &&\n                    (special_mode || tax.price_include === batch[0].price_include) &&\n                    tax.include_base_amount === batch[0].include_base_amount &&\n                    ((tax.include_base_amount && !is_base_affected) || !tax.include_base_amount);\n                if (!same_batch) {\n                    for (const batch_tax of batch) {\n                        results.batch_per_tax[batch_tax.id] = batch;\n                    }\n                    batch = [];\n                }\n            }\n\n            is_base_affected = tax.is_base_affected;\n            batch.push(tax);\n        }\n\n        if (batch.length !== 0) {\n            for (const batch_tax of batch) {\n                results.batch_per_tax[batch_tax.id] = batch;\n            }\n        }\n        return results;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    propagate_extra_taxes_base(taxes, tax, taxes_data, { special_mode = null } = {}) {\n        function* get_tax_before() {\n            for (const tax_before of taxes) {\n                if (taxes_data[tax.id].batch.includes(tax_before)) {\n                    break;\n                }\n                yield tax_before;\n            }\n        }\n\n        function* get_tax_after() {\n            for (const tax_after of taxes.toReversed()) {\n                if (taxes_data[tax.id].batch.includes(tax_after)) {\n                    break;\n                }\n                yield tax_after;\n            }\n        }\n\n        function add_extra_base(other_tax, sign) {\n            const tax_amount = taxes_data[tax.id].tax_amount;\n            if (!(\"tax_amount\" in taxes_data[other_tax.id])) {\n                taxes_data[other_tax.id].extra_base_for_tax += sign * tax_amount;\n            }\n            taxes_data[other_tax.id].extra_base_for_base += sign * tax_amount;\n        }\n\n        if (tax.price_include) {\n            // Case: special mode is False or 'total_included'\n            if (!special_mode || special_mode === \"total_included\") {\n                if (tax.include_base_amount) {\n                    for (const other_tax of get_tax_after()) {\n                        if (!other_tax.is_base_affected) {\n                            add_extra_base(other_tax, -1);\n                        }\n                    }\n                } else {\n                    for (const other_tax of get_tax_after()) {\n                        add_extra_base(other_tax, -1);\n                    }\n                }\n                for (const other_tax of get_tax_before()) {\n                    add_extra_base(other_tax, -1);\n                }\n\n                // Case: special_mode = 'total_excluded'\n            } else {\n                if (tax.include_base_amount) {\n                    for (const other_tax of get_tax_after()) {\n                        if (other_tax.is_base_affected) {\n                            add_extra_base(other_tax, 1);\n                        }\n                    }\n                }\n            }\n        } else if (!tax.price_include) {\n            // Case: special_mode is False or 'total_excluded'\n            if (!special_mode || special_mode === \"total_excluded\") {\n                if (tax.include_base_amount) {\n                    for (const other_tax of get_tax_after()) {\n                        if (other_tax.is_base_affected) {\n                            add_extra_base(other_tax, 1);\n                        }\n                    }\n                }\n\n                // Case: special_mode = 'total_included'\n            } else {\n                if (!tax.include_base_amount) {\n                    for (const other_tax of get_tax_after()) {\n                        add_extra_base(other_tax, -1);\n                    }\n                }\n                for (const other_tax of get_tax_before()) {\n                    add_extra_base(other_tax, -1);\n                }\n            }\n        }\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    eval_tax_amount_fixed_amount(tax, batch, raw_base, evaluation_context) {\n        if (tax.amount_type === \"fixed\") {\n            const sign = evaluation_context.price_unit < 0.0 ? -1 : 1;\n            return sign * evaluation_context.quantity * tax.amount;\n        }\n        return null;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    eval_tax_amount_price_included(tax, batch, raw_base, evaluation_context) {\n        if (tax.amount_type === \"percent\") {\n            const total_percentage =\n                batch.reduce((sum, batch_tax) => sum + batch_tax.amount, 0) / 100.0;\n            const to_price_excluded_factor =\n                total_percentage !== -1 ? 1 / (1 + total_percentage) : 0.0;\n            return (raw_base * to_price_excluded_factor * tax.amount) / 100.0;\n        }\n\n        if (tax.amount_type === \"division\") {\n            return (raw_base * tax.amount) / 100.0;\n        }\n        return null;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    eval_tax_amount_price_excluded(tax, batch, raw_base, evaluation_context) {\n        if (tax.amount_type === \"percent\") {\n            return (raw_base * tax.amount) / 100.0;\n        }\n\n        if (tax.amount_type === \"division\") {\n            const total_percentage =\n                batch.reduce((sum, batch_tax) => sum + batch_tax.amount, 0) / 100.0;\n            const incl_base_multiplicator = total_percentage === 1.0 ? 1.0 : 1 - total_percentage;\n            return (raw_base * tax.amount) / 100.0 / incl_base_multiplicator;\n        }\n        return null;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    get_tax_details(\n        taxes,\n        price_unit,\n        quantity,\n        {\n            precision_rounding = null,\n            rounding_method = \"round_per_line\",\n            // When product is null, we need the product default values to make the \"formula\" taxes\n            // working. In that case, we need to deal with the product default values before calling this\n            // method because we have no way to deal with it automatically in this method since it depends of\n            // the type of involved fields and we don't have access to this information js-side.\n            product = null,\n            product_uom = null,\n            special_mode = null,\n            manual_tax_amounts = null, // TO BE REMOVED IN MASTER\n            filter_tax_function = null,\n        } = {}\n    ) {\n        const self = this;\n\n        function add_tax_amount_to_results(tax, tax_amount) {\n            taxes_data[tax.id].tax_amount = tax_amount;\n            if (rounding_method === \"round_per_line\") {\n                taxes_data[tax.id].tax_amount = roundPrecision(\n                    taxes_data[tax.id].tax_amount,\n                    precision_rounding\n                );\n            }\n            if (tax.has_negative_factor) {\n                reverse_charge_taxes_data[tax.id].tax_amount = -taxes_data[tax.id].tax_amount;\n            }\n\n            self.propagate_extra_taxes_base(sorted_taxes, tax, taxes_data, {\n                special_mode: special_mode,\n            });\n        }\n\n        function eval_tax_amount(tax_amount_function, tax) {\n            const is_already_computed = \"tax_amount\" in taxes_data[tax.id];\n            if (is_already_computed) {\n                return;\n            }\n\n            const tax_amount = tax_amount_function(\n                tax,\n                taxes_data[tax.id].batch,\n                raw_base + taxes_data[tax.id].extra_base_for_tax,\n                evaluation_context\n            );\n            if (tax_amount !== null) {\n                add_tax_amount_to_results(tax, tax_amount);\n            }\n        }\n\n        // Flatten the taxes, order them and filter them if necessary.\n\n        function prepare_tax_extra_data(tax, kwargs = {}) {\n            let price_include;\n            if (tax.has_negative_factor) {\n                price_include = false;\n            } else if (special_mode === \"total_included\") {\n                price_include = true;\n            } else if (special_mode === \"total_excluded\") {\n                price_include = false;\n            } else {\n                price_include = tax.price_include;\n            }\n            return {\n                ...kwargs,\n                tax: tax,\n                price_include: price_include,\n                extra_base_for_tax: 0.0,\n                extra_base_for_base: 0.0,\n            };\n        }\n\n        const batching_results = this.batch_for_taxes_computation(taxes, {\n            special_mode: special_mode,\n            filter_tax_function: filter_tax_function,\n        });\n        let sorted_taxes = batching_results.sorted_taxes;\n        const taxes_data = {};\n        const reverse_charge_taxes_data = {};\n        for (const tax of sorted_taxes) {\n            taxes_data[tax.id] = prepare_tax_extra_data(tax, {\n                group: batching_results.group_per_tax[tax.id],\n                batch: batching_results.batch_per_tax[tax.id],\n            });\n            if (tax.has_negative_factor) {\n                reverse_charge_taxes_data[tax.id] = {\n                    ...taxes_data[tax.id],\n                    is_reverse_charge: true,\n                };\n            }\n        }\n\n        let raw_base = quantity * price_unit;\n        if (rounding_method === \"round_per_line\") {\n            raw_base = roundPrecision(raw_base, precision_rounding);\n        }\n\n        let evaluation_context = {\n            product: product || {},\n            uom: product_uom || {},\n            price_unit: price_unit,\n            quantity: quantity,\n            raw_base: raw_base,\n            special_mode: special_mode,\n        };\n\n        // Define the order in which the taxes must be evaluated.\n        // Fixed taxes are computed directly because they could affect the base of a price included batch right after.\n        for (const tax of sorted_taxes.toReversed()) {\n            eval_tax_amount(this.eval_tax_amount_fixed_amount.bind(this), tax);\n        }\n\n        // Then, let's travel the batches in the reverse order and process the price-included taxes.\n        for (const tax of sorted_taxes.toReversed()) {\n            if (taxes_data[tax.id].price_include) {\n                eval_tax_amount(this.eval_tax_amount_price_included.bind(this), tax);\n            }\n        }\n\n        // Then, let's travel the batches in the normal order and process the price-excluded taxes.\n        for (const tax of sorted_taxes) {\n            if (!taxes_data[tax.id].price_include) {\n                eval_tax_amount(this.eval_tax_amount_price_excluded.bind(this), tax);\n            }\n        }\n\n        // Mark the base to be computed in the descending order. The order doesn't matter for no special mode or 'total_excluded' but\n        // it must be in the reverse order when special_mode is 'total_included'.\n        const subsequent_taxes = [];\n        for (const tax of sorted_taxes.toReversed()) {\n            const tax_data = taxes_data[tax.id];\n            if (!(\"tax_amount\" in tax_data)) {\n                continue;\n            }\n\n            // Base amount.\n            const total_tax_amount =\n                taxes_data[tax.id].batch.reduce(\n                    (sum, other_tax) => sum + taxes_data[other_tax.id].tax_amount,\n                    0\n                ) +\n                Object.values(taxes_data[tax.id].batch)\n                    .filter((other_tax) => other_tax.has_negative_factor)\n                    .reduce(\n                        (sum, other_tax) =>\n                            sum + reverse_charge_taxes_data[other_tax.id].tax_amount,\n                        0\n                    );\n            let base = raw_base + taxes_data[tax.id].extra_base_for_base;\n            if (tax_data.price_include && (!special_mode || special_mode === \"total_included\")) {\n                base -= total_tax_amount;\n            }\n            tax_data.base = base;\n\n            // Subsequence taxes.\n            tax_data.taxes = [];\n            if (tax.include_base_amount) {\n                tax_data.taxes.push(...subsequent_taxes);\n            }\n\n            // Reverse charge.\n            if (tax.has_negative_factor) {\n                const reverse_charge_tax_data = reverse_charge_taxes_data[tax.id];\n                reverse_charge_tax_data.base = base;\n                reverse_charge_tax_data.taxes = tax_data.taxes;\n            }\n\n            if (tax.is_base_affected) {\n                subsequent_taxes.push(tax);\n            }\n        }\n\n        const taxes_data_list = [];\n        for (const tax of sorted_taxes) {\n            const tax_data = taxes_data[tax.id];\n            if (\"tax_amount\" in tax_data) {\n                taxes_data_list.push(tax_data);\n                if (tax.has_negative_factor) {\n                    taxes_data_list.push(reverse_charge_taxes_data[tax.id]);\n                }\n            }\n        }\n\n        let total_excluded, total_included;\n        if (taxes_data_list.length > 0) {\n            total_excluded = taxes_data_list[0].base;\n            const tax_amount = taxes_data_list.reduce(\n                (sum, tax_data) => sum + tax_data.tax_amount,\n                0\n            );\n            total_included = total_excluded + tax_amount;\n        } else {\n            total_excluded = total_included = raw_base;\n        }\n\n        return {\n            total_excluded: total_excluded,\n            total_included: total_included,\n            taxes_data: taxes_data_list.map((tax_data) =>\n                Object.assign(\n                    {},\n                    {\n                        tax: tax_data.tax,\n                        taxes: tax_data.taxes,\n                        group: batching_results.group_per_tax[tax_data.tax.id],\n                        batch: batching_results.batch_per_tax[tax_data.tax.id],\n                        tax_amount: tax_data.tax_amount,\n                        price_include: tax_data.price_include,\n                        base_amount: tax_data.base,\n                        is_reverse_charge: tax_data.is_reverse_charge || false,\n                    }\n                )\n            ),\n        };\n    },\n\n    // -------------------------------------------------------------------------\n    // MAPPING PRICE_UNIT\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    adapt_price_unit_to_another_taxes(price_unit, product, original_taxes, new_taxes, { product_uom = null } = {}) {\n        const original_tax_ids = new Set(original_taxes.map((x) => x.id));\n        const new_tax_ids = new Set(new_taxes.map((x) => x.id));\n        if (\n            (original_tax_ids.size === new_tax_ids.size &&\n                [...original_tax_ids].every((value) => new_tax_ids.has(value))) ||\n            original_taxes.some((x) => !x.price_include)\n        ) {\n            return price_unit;\n        }\n\n        // Find the price unit without tax.\n        let taxes_computation = this.get_tax_details(original_taxes, price_unit, 1.0, {\n            rounding_method: \"round_globally\",\n            product: product,\n            product_uom: product_uom,\n        });\n        price_unit = taxes_computation.total_excluded;\n\n        // Find the new price unit after applying the price included taxes.\n        taxes_computation = this.get_tax_details(new_taxes, price_unit, 1.0, {\n            rounding_method: \"round_globally\",\n            product: product,\n            product_uom: product_uom,\n            special_mode: \"total_excluded\",\n        });\n        let delta = 0.0;\n        for (const tax_data of taxes_computation.taxes_data) {\n            if (tax_data.tax.price_include) {\n                delta += tax_data.tax_amount;\n            }\n        }\n        return price_unit + delta;\n    },\n\n    // -------------------------------------------------------------------------\n    // GENERIC REPRESENTATION OF BUSINESS OBJECTS & METHODS\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    export_base_line_extra_tax_data(base_line) {\n        const results = {};\n        if (base_line.computation_key) {\n            results.computation_key = base_line.computation_key;\n        }\n\n        let store_source_data = false;\n        if (base_line.manual_total_excluded_currency !== null) {\n            results.manual_total_excluded_currency = base_line.manual_total_excluded_currency;\n            store_source_data = true;\n        }\n        if (base_line.manual_total_excluded !== null) {\n            results.manual_total_excluded = base_line.manual_total_excluded;\n            store_source_data = true;\n        }\n        if (base_line.manual_tax_amounts && Object.keys(base_line.manual_tax_amounts).length > 0) {\n            results.manual_tax_amounts = base_line.manual_tax_amounts;\n            store_source_data = true;\n        }\n\n        if (store_source_data) {\n            Object.assign(results, {\n                currency_id: base_line.currency_id.id,\n                price_unit: base_line.price_unit,\n                discount: base_line.discount,\n                quantity: base_line.quantity,\n                rate: base_line.rate,\n            });\n        }\n        return results;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    import_base_line_extra_tax_data(base_line, extra_tax_data) {\n        const currency_dp = base_line.currency_id.decimal_places;\n\n        // compare_amounts does not exist in numbers.js\n        const are_amounts_equal = (a, b) =>\n            floatIsZero(\n                roundPrecision(a, currency_dp) - roundPrecision(b, currency_dp),\n                currency_dp\n            );\n\n        const results = {};\n\n        if (extra_tax_data && extra_tax_data.computation_key) {\n            results.computation_key = extra_tax_data.computation_key;\n        }\n\n        const manual_tax_amounts = extra_tax_data ? extra_tax_data.manual_tax_amounts || {} : null;\n        const extra_tax_data_tax_ids = new Set(Object.keys(manual_tax_amounts || {}));\n        const { sorted_taxes } = this.flatten_taxes_and_sort_them(base_line.tax_ids);\n        if (\n            extra_tax_data &&\n            extra_tax_data.currency_id &&\n            base_line.currency_id.id === extra_tax_data.currency_id &&\n            are_amounts_equal(base_line.price_unit, extra_tax_data.price_unit) &&\n            are_amounts_equal(base_line.discount, extra_tax_data.discount) &&\n            are_amounts_equal(base_line.quantity, extra_tax_data.quantity) &&\n            sorted_taxes.length === extra_tax_data_tax_ids.size &&\n            sorted_taxes\n                .map((tax) => tax.id.toString())\n                .every((tax_id_str) => extra_tax_data_tax_ids.has(tax_id_str))\n        ) {\n            results.price_unit = extra_tax_data.price_unit;\n\n            let delta_rate;\n            if (base_line.rate && extra_tax_data.rate) {\n                delta_rate = base_line.rate / extra_tax_data.rate;\n            } else {\n                delta_rate = 1.0;\n            }\n\n            if (\"manual_total_excluded_currency\" in extra_tax_data) {\n                results.manual_total_excluded_currency =\n                    extra_tax_data.manual_total_excluded_currency;\n            }\n            if (\"manual_total_excluded\" in extra_tax_data) {\n                results.manual_total_excluded = extra_tax_data.manual_total_excluded / delta_rate;\n            }\n\n            if (manual_tax_amounts) {\n                results.manual_tax_amounts = {};\n                for (const [tax_id_str, amounts] of Object.entries(\n                    extra_tax_data.manual_tax_amounts\n                )) {\n                    results.manual_tax_amounts[tax_id_str] = { ...amounts };\n                    if (\"tax_amount\" in amounts) {\n                        results.manual_tax_amounts[tax_id_str].tax_amount /= delta_rate;\n                    }\n                    if (\"base_amount\" in amounts) {\n                        results.manual_tax_amounts[tax_id_str].base_amount /= delta_rate;\n                    }\n                }\n            }\n        }\n        return results;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    get_base_line_field_value_from_record(record, field, extra_values, fallback) {\n        if (field in extra_values) {\n            return extra_values[field] || fallback;\n        }\n        if (record && field in record) {\n            return record[field] || fallback;\n        }\n        return fallback;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    prepare_base_line_for_taxes_computation(record, kwargs = {}) {\n        const load = (field, fallback) =>\n            this.get_base_line_field_value_from_record(record, field, kwargs, fallback);\n        const currency =\n            load(\"currency_id\", null) ||\n            load(\"company_currency_id\", null) ||\n            load(\"company_id\", {}).currency_id ||\n            {};\n\n        const base_line = {\n            ...kwargs,\n            record: record,\n            id: load(\"id\", 0),\n            product_id: load(\"product_id\", {}),\n            product_uom_id: load(\"product_uom_id\", {}),\n            tax_ids: load(\"tax_ids\", []),\n            price_unit: load(\"price_unit\", 0.0),\n            quantity: load(\"quantity\", 0.0),\n            discount: load(\"discount\", 0.0),\n            currency_id: currency,\n            sign: load(\"sign\", 1.0),\n            special_mode: kwargs.special_mode || null,\n            special_type: kwargs.special_type || null,\n            rate: load(\"rate\", 1.0),\n            filter_tax_function: kwargs.filter_tax_function || null,\n        };\n\n        const extra_tax_data = this.import_base_line_extra_tax_data(\n            base_line,\n            load(\"extra_tax_data\", {}) || {}\n        );\n        Object.assign(base_line, {\n            manual_total_excluded_currency:\n                kwargs.manual_total_excluded_currency ||\n                extra_tax_data.manual_total_excluded_currency ||\n                null,\n            manual_total_excluded:\n                kwargs.manual_total_excluded || extra_tax_data.manual_total_excluded || null,\n            computation_key: kwargs.computation_key || extra_tax_data.computation_key || null,\n            manual_tax_amounts:\n                kwargs.manual_tax_amounts || extra_tax_data.manual_tax_amounts || null,\n        });\n        if (\"price_unit\" in extra_tax_data) {\n            base_line.price_unit = extra_tax_data.price_unit;\n        }\n\n        // Propagate custom values.\n        if (record && typeof record === \"object\") {\n            for (const [k, v] of Object.entries(record)) {\n                if (k && typeof k === \"string\" && k.startsWith(\"_\") && !(k in base_line)) {\n                    base_line[k] = v;\n                }\n            }\n        }\n\n        return base_line;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    add_tax_details_in_base_line(base_line, company, { rounding_method = null } = {}) {\n        rounding_method = rounding_method || company.tax_calculation_rounding_method;\n        const price_unit_after_discount = base_line.price_unit * (1 - base_line.discount / 100.0);\n        const currency_pd = base_line.currency_id.rounding;\n        const company_currency_pd = company.currency_id.rounding;\n        const taxes_computation = this.get_tax_details(\n            base_line.tax_ids,\n            price_unit_after_discount,\n            base_line.quantity,\n            {\n                precision_rounding: currency_pd,\n                rounding_method: rounding_method,\n                product: base_line.product_id,\n                product_uom: base_line.product_uom_id,\n                special_mode: base_line.special_mode,\n                filter_tax_function: base_line.filter_tax_function,\n            }\n        );\n\n        const rate = base_line.rate;\n        const tax_details = (base_line.tax_details = {\n            raw_total_excluded_currency: taxes_computation.total_excluded,\n            raw_total_excluded: rate ? taxes_computation.total_excluded / rate : 0.0,\n            raw_total_included_currency: taxes_computation.total_included,\n            raw_total_included: rate ? taxes_computation.total_included / rate : 0.0,\n            taxes_data: [],\n        });\n\n        if (rounding_method === \"round_per_line\") {\n            tax_details.raw_total_excluded = roundPrecision(\n                tax_details.raw_total_excluded,\n                currency_pd\n            );\n            tax_details.raw_total_included = roundPrecision(\n                tax_details.raw_total_included,\n                currency_pd\n            );\n        }\n\n        for (const tax_data of taxes_computation.taxes_data) {\n            let tax_amount = rate ? tax_data.tax_amount / rate : 0.0;\n            let base_amount = rate ? tax_data.base_amount / rate : 0.0;\n\n            if (rounding_method === \"round_per_line\") {\n                tax_amount = roundPrecision(tax_amount, company_currency_pd);\n                base_amount = roundPrecision(base_amount, company_currency_pd);\n            }\n\n            tax_details.taxes_data.push({\n                ...tax_data,\n                raw_tax_amount_currency: tax_data.tax_amount,\n                raw_tax_amount: tax_amount,\n                raw_base_amount_currency: tax_data.base_amount,\n                raw_base_amount: base_amount,\n            });\n        }\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    add_tax_details_in_base_lines(base_lines, company) {\n        for (const base_line of base_lines) {\n            this.add_tax_details_in_base_line(base_line, company);\n        }\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    normalize_target_factors(target_factors) {\n        const factors = target_factors.map((x, i) => [i, Math.abs(x.factor)]);\n        factors.sort((a, b) => b[1] - a[1]);\n        const sum_of_factors = factors.reduce((sum, x) => sum + x[1], 0.0);\n        return factors.map((x) => [x[0], sum_of_factors ? x[1] / sum_of_factors : 0.0]);\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    distribute_delta_amount_smoothly(precision_digits, delta_amount, target_factors) {\n        const precision_rounding = Number(`1e-${precision_digits}`);\n        const amounts_to_distribute = target_factors.map((x) => 0.0);\n        if (floatIsZero(delta_amount, precision_digits)) {\n            return amounts_to_distribute;\n        }\n\n        const sign = delta_amount < 0.0 ? -1 : 1;\n        const nb_of_errors = Math.round(Math.abs(delta_amount / precision_rounding));\n        let remaining_errors = nb_of_errors;\n\n        // Distribute using the factor first.\n        const factors = this.normalize_target_factors(target_factors);\n        for (const [i, factor] of factors) {\n            if (!remaining_errors) {\n                break;\n            }\n\n            const nb_of_amount_to_distribute = Math.min(\n                Math.round(factor * nb_of_errors),\n                remaining_errors\n            );\n            remaining_errors -= nb_of_amount_to_distribute;\n            const amount_to_distribute = sign * nb_of_amount_to_distribute * precision_rounding;\n            amounts_to_distribute[i] += amount_to_distribute;\n        }\n\n        // Distribute the remaining cents across the factors.\n        // There are sorted by the biggest first.\n        // Since the factors are normalized, the residual number of cents can't be higher than the number of factors.\n        for (let i = 0; i < remaining_errors; i++) {\n            amounts_to_distribute[factors[i][0]] += sign * precision_rounding;\n        }\n\n        return amounts_to_distribute;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    round_tax_details_tax_amounts(base_lines, company, { mode = \"mixed\" } = {}) {\n        function grouping_function(base_line, tax_data) {\n            if (!tax_data) {\n                return;\n            }\n            return {\n                is_refund: base_line.is_refund,\n                is_reverse_charge: tax_data.is_reverse_charge,\n                price_include: tax_data.price_include,\n                computation_key: base_line.computation_key,\n                tax: tax_data.tax,\n                currency: base_line.currency_id,\n            };\n        }\n\n        const base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            base_lines,\n            grouping_function\n        );\n        const values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n        for (const values of Object.values(values_per_grouping_key)) {\n            const grouping_key = values.grouping_key;\n            if (!grouping_key) {\n                continue;\n            }\n\n            const price_include = grouping_key.price_include;\n            const currency = grouping_key.currency;\n            for (const [delta_currency_indicator, delta_currency] of [\n                [\"_currency\", currency],\n                [\"\", company.currency_id],\n            ]) {\n                // Tax amount\n                const raw_total_tax_amount = values[`target_tax_amount${delta_currency_indicator}`];\n                const rounded_raw_total_tax_amount = roundPrecision(\n                    raw_total_tax_amount,\n                    delta_currency.rounding\n                );\n                const total_tax_amount = values[`tax_amount${delta_currency_indicator}`];\n                const delta_total_tax_amount = rounded_raw_total_tax_amount - total_tax_amount;\n\n                if (raw_total_tax_amount) {\n                    const target_factors = values.base_line_x_taxes_data.flatMap(\n                        ([_, taxes_data]) =>\n                            taxes_data.map((tax_data) => ({\n                                factor: tax_data[`raw_tax_amount${delta_currency_indicator}`],\n                                tax_data: tax_data,\n                            }))\n                    );\n\n                    const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                        delta_currency.decimal_places,\n                        delta_total_tax_amount,\n                        target_factors\n                    );\n\n                    for (let i = 0; i < target_factors.length; i++) {\n                        const tax_data = target_factors[i].tax_data;\n                        const amount_to_distribute = amounts_to_distribute[i];\n                        tax_data[`tax_amount${delta_currency_indicator}`] += amount_to_distribute;\n                    }\n                }\n\n                // Base amount\n                const raw_total_base_amount =\n                    values[`target_base_amount${delta_currency_indicator}`];\n                let delta_total_base_amount = 0.0;\n\n                if ((mode === \"mixed\" && price_include) || mode === \"included\") {\n                    const raw_total_amount = raw_total_base_amount + raw_total_tax_amount;\n                    const rounded_raw_total_amount = roundPrecision(\n                        raw_total_amount,\n                        delta_currency.rounding\n                    );\n                    const total_amount =\n                        values[`base_amount${delta_currency_indicator}`] +\n                        total_tax_amount +\n                        delta_total_tax_amount;\n                    delta_total_base_amount = rounded_raw_total_amount - total_amount;\n                } else if ((mode === \"mixed\" && !price_include) || mode === \"excluded\") {\n                    const rounded_raw_total_base_amount = roundPrecision(\n                        raw_total_base_amount,\n                        delta_currency.rounding\n                    );\n                    const total_base_amount = values[`base_amount${delta_currency_indicator}`];\n                    delta_total_base_amount = rounded_raw_total_base_amount - total_base_amount;\n                }\n\n                if (raw_total_base_amount) {\n                    const target_factors = values.base_line_x_taxes_data.flatMap(\n                        ([_, taxes_data]) =>\n                            taxes_data.map((tax_data) => ({\n                                factor: tax_data[`raw_base_amount${delta_currency_indicator}`],\n                                tax_data: tax_data,\n                            }))\n                    );\n\n                    const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                        delta_currency.decimal_places,\n                        delta_total_base_amount,\n                        target_factors\n                    );\n\n                    for (let i = 0; i < target_factors.length; i++) {\n                        const tax_data = target_factors[i].tax_data;\n                        const amount_to_distribute = amounts_to_distribute[i];\n                        tax_data[`base_amount${delta_currency_indicator}`] += amount_to_distribute;\n                    }\n                }\n            }\n        }\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    round_tax_details_base_lines(base_lines, company, { mode = \"mixed\" } = {}) {\n        function grouping_function(base_line, tax_data) {\n            return {\n                is_refund: base_line.is_refund,\n                currency: base_line.currency_id,\n                computation_key: base_line.computation_key,\n            };\n        }\n\n        const base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            base_lines,\n            grouping_function\n        );\n        const values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n        for (const values of Object.values(values_per_grouping_key)) {\n            const grouping_key = values.grouping_key;\n            let current_mode = mode;\n            if (current_mode === \"mixed\") {\n                current_mode = \"included\";\n                for (const base_line_taxes_data of values.base_line_x_taxes_data) {\n                    const taxes_data = base_line_taxes_data[1];\n                    if (taxes_data.some((tax_data) => !tax_data.price_include)) {\n                        current_mode = \"excluded\";\n                        break;\n                    }\n                }\n            }\n\n            const currency = grouping_key.currency;\n            for (const [delta_currency_indicator, delta_currency] of [\n                [\"_currency\", currency],\n                [\"\", company.currency_id],\n            ]) {\n                let delta_total_excluded = 0.0;\n                let target_factors = [];\n                if (current_mode === \"excluded\") {\n                    // Price-excluded rounding.\n                    const raw_total_excluded =\n                        values[`target_total_excluded${delta_currency_indicator}`];\n                    if (!raw_total_excluded) {\n                        continue;\n                    }\n\n                    const rounded_raw_total_excluded = roundPrecision(\n                        raw_total_excluded,\n                        delta_currency.rounding\n                    );\n                    const total_excluded = values[`total_excluded${delta_currency_indicator}`];\n                    delta_total_excluded = rounded_raw_total_excluded - total_excluded;\n                    target_factors = values.base_line_x_taxes_data.map(([base_line]) => ({\n                        factor: base_line.tax_details[\n                            `raw_total_excluded${delta_currency_indicator}`\n                        ],\n                        base_line: base_line,\n                    }));\n                } else {\n                    // Price-included rounding.\n                    const raw_total_included =\n                        values[`target_total_excluded${delta_currency_indicator}`] +\n                        values[`target_tax_amount${delta_currency_indicator}`];\n                    if (!raw_total_included) {\n                        continue;\n                    }\n                    const rounded_raw_total_included = roundPrecision(\n                        raw_total_included,\n                        delta_currency.rounding\n                    );\n                    const total_included =\n                        values[`total_excluded${delta_currency_indicator}`] +\n                        values[`tax_amount${delta_currency_indicator}`];\n                    delta_total_excluded = rounded_raw_total_included - total_included;\n                    target_factors = values.base_line_x_taxes_data.map(([base_line]) => ({\n                        factor: base_line.tax_details[\n                            `raw_total_included${delta_currency_indicator}`\n                        ],\n                        base_line: base_line,\n                    }));\n                }\n\n                const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                    delta_currency.decimal_places,\n                    delta_total_excluded,\n                    target_factors\n                );\n                for (let i = 0; i < target_factors.length; i++) {\n                    const base_line = target_factors[i].base_line;\n                    const amount_to_distribute = amounts_to_distribute[i];\n                    base_line.tax_details[`delta_total_excluded${delta_currency_indicator}`] +=\n                        amount_to_distribute;\n                }\n            }\n        }\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    round_base_lines_tax_details(base_lines, company) {\n        // Raw rounding.\n        for (const base_line of base_lines) {\n            const tax_details = base_line.tax_details;\n\n            for (const [suffix, currency] of [\n                [\"_currency\", base_line.currency_id],\n                [\"\", company.currency_id],\n            ]) {\n                const total_excluded_field = `total_excluded${suffix}`;\n                tax_details[total_excluded_field] = roundPrecision(\n                    tax_details[`raw_${total_excluded_field}`],\n                    currency.rounding\n                );\n\n                for (const tax_data of tax_details.taxes_data) {\n                    for (const prefix of [\"base\", \"tax\"]) {\n                        const field = `${prefix}_amount${suffix}`;\n                        tax_data[field] = roundPrecision(\n                            tax_data[`raw_${field}`],\n                            currency.rounding\n                        );\n                    }\n                }\n            }\n        }\n\n        // Apply 'manual_tax_amounts'.\n        for (const base_line of base_lines) {\n            const manual_tax_amounts = base_line.manual_tax_amounts;\n            const rate = base_line.rate;\n            const tax_details = base_line.tax_details;\n\n            for (const [suffix, currency] of [\n                [\"_currency\", base_line.currency_id],\n                [\"\", company.currency_id],\n            ]) {\n                const total_field = `total_excluded${suffix}`;\n                const manual_field = `manual_${total_field}`;\n                if (base_line[manual_field] !== null) {\n                    tax_details[total_field] = base_line[manual_field];\n                    if (suffix === \"_currency\" && rate) {\n                        tax_details.total_excluded = roundPrecision(\n                            tax_details[total_field] / rate,\n                            company.currency_id.rounding\n                        );\n                    }\n                }\n\n                for (const tax_data of tax_details.taxes_data) {\n                    const tax = tax_data.tax;\n                    const reverse_charge_sign = tax_data.is_reverse_charge ? -1 : 1;\n                    const current_manual_tax_amounts =\n                        (manual_tax_amounts && manual_tax_amounts[String(tax.id)]) || {};\n\n                    for (const [prefix, factor] of [\n                        [\"base\", 1],\n                        [\"tax\", reverse_charge_sign],\n                    ]) {\n                        const field = `${prefix}_amount${suffix}`;\n                        if (field in current_manual_tax_amounts) {\n                            tax_data[field] = roundPrecision(\n                                factor * current_manual_tax_amounts[field],\n                                currency.rounding\n                            );\n                            if (suffix === \"_currency\" && rate) {\n                                tax_data[`${prefix}_amount`] = roundPrecision(\n                                    tax_data[field] / rate,\n                                    company.currency_id.rounding\n                                );\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        // Compute 'total_included' & add 'delta_total_excluded'.\n        for (const base_line of base_lines) {\n            const tax_details = base_line.tax_details;\n            for (const suffix of [\"_currency\", \"\"]) {\n                tax_details[`delta_total_excluded${suffix}`] = 0.0;\n                tax_details[`total_included${suffix}`] = tax_details[`total_excluded${suffix}`];\n                for (const tax_data of tax_details.taxes_data) {\n                    tax_details[`total_included${suffix}`] += tax_data[`tax_amount${suffix}`];\n                }\n            }\n        }\n\n        this.round_tax_details_tax_amounts(base_lines, company);\n        this.round_tax_details_base_lines(base_lines, company);\n    },\n\n    // -------------------------------------------------------------------------\n    // TAX TOTALS SUMMARY\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    get_tax_totals_summary(base_lines, currency, company, { cash_rounding = null } = {}) {\n        const company_pd = company.currency_id.rounding;\n        const tax_totals_summary = {\n            currency_id: currency.id,\n            currency_pd: currency.rounding,\n            company_currency_id: company.currency_id.id,\n            company_currency_pd: company.currency_id.rounding,\n            has_tax_groups: false,\n            subtotals: [],\n            base_amount_currency: 0.0,\n            base_amount: 0.0,\n            tax_amount_currency: 0.0,\n            tax_amount: 0.0,\n        };\n\n        // Global tax values.\n        const global_grouping_function = (base_line, tax_data) => tax_data !== null;\n\n        let base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            base_lines,\n            global_grouping_function\n        );\n        let values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n\n        for (const values of Object.values(values_per_grouping_key)) {\n            if (values.grouping_key) {\n                tax_totals_summary.has_tax_groups = true;\n            }\n            tax_totals_summary.base_amount_currency += values.total_excluded_currency;\n            tax_totals_summary.base_amount += values.total_excluded;\n            tax_totals_summary.tax_amount_currency += values.tax_amount_currency;\n            tax_totals_summary.tax_amount += values.tax_amount;\n        }\n\n        // Tax groups.\n        const untaxed_amount_subtotal_label = _t(\"Untaxed Amount\");\n        const subtotals = {};\n\n        const tax_group_grouping_function = (base_line, tax_data) => {\n            if (!tax_data) {\n                return;\n            }\n            return tax_data.tax.tax_group_id;\n        };\n\n        base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            base_lines,\n            tax_group_grouping_function\n        );\n        values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n\n        const sorted_total_per_tax_group = Object.values(values_per_grouping_key)\n            .filter((values) => values.grouping_key)\n            .sort(\n                (a, b) =>\n                    a.grouping_key.sequence - b.grouping_key.sequence ||\n                    a.grouping_key.id - b.grouping_key.id\n            );\n\n        const encountered_base_amounts = new Set();\n        const subtotals_order = {};\n\n        for (const [order, values] of sorted_total_per_tax_group.entries()) {\n            const tax_group = values.grouping_key;\n\n            // Get all involved taxes in the tax group.\n            const involved_tax_ids = new Set();\n            const involved_amount_types = new Set();\n            const involved_price_include = new Set();\n            values.base_line_x_taxes_data.forEach(([base_line, taxes_data]) => {\n                taxes_data.forEach((tax_data) => {\n                    const tax = tax_data.tax;\n                    involved_tax_ids.add(tax.id);\n                    involved_amount_types.add(tax.amount_type);\n                    involved_price_include.add(tax.price_include);\n                });\n            });\n\n            // Compute the display base amounts.\n            let display_base_amount;\n            let display_base_amount_currency;\n            if (involved_amount_types.size === 1 && involved_amount_types.has(\"fixed\")) {\n                display_base_amount = false;\n                display_base_amount_currency = false;\n            } else if (\n                involved_amount_types.size === 1 &&\n                involved_amount_types.has(\"division\") &&\n                involved_price_include.size === 1 &&\n                involved_price_include.has(true)\n            ) {\n                display_base_amount = 0.0;\n                display_base_amount_currency = 0.0;\n                values.base_line_x_taxes_data.forEach(([base_line, _taxes_data]) => {\n                    const tax_details = base_line.tax_details;\n                    display_base_amount +=\n                        tax_details.total_excluded + tax_details.delta_total_excluded;\n                    display_base_amount_currency +=\n                        tax_details.total_excluded_currency +\n                        tax_details.delta_total_excluded_currency;\n                    for (const tax_data of tax_details.taxes_data) {\n                        display_base_amount_currency += tax_data.tax_amount_currency;\n                        display_base_amount += tax_data.tax_amount;\n                    }\n                });\n            } else {\n                display_base_amount = values.base_amount;\n                display_base_amount_currency = values.base_amount_currency;\n            }\n\n            if (typeof display_base_amount_currency === \"number\") {\n                encountered_base_amounts.add(\n                    parseFloat(display_base_amount_currency.toFixed(currency.decimal_places))\n                );\n            }\n\n            // Order of the subtotals.\n            const preceding_subtotal =\n                tax_group.preceding_subtotal || untaxed_amount_subtotal_label;\n            if (!(preceding_subtotal in subtotals)) {\n                subtotals[preceding_subtotal] = {\n                    tax_groups: [],\n                    tax_amount_currency: 0.0,\n                    tax_amount: 0.0,\n                    base_amount_currency: 0.0,\n                    base_amount: 0.0,\n                };\n            }\n            if (!(preceding_subtotal in subtotals_order)) {\n                subtotals_order[preceding_subtotal] = order;\n            }\n\n            subtotals[preceding_subtotal].tax_groups.push({\n                id: tax_group.id,\n                involved_tax_ids: Array.from(involved_tax_ids),\n                tax_amount_currency: values.tax_amount_currency,\n                tax_amount: values.tax_amount,\n                base_amount_currency: values.base_amount_currency,\n                base_amount: values.base_amount,\n                display_base_amount_currency,\n                display_base_amount,\n                group_name: tax_group.name,\n                group_label: tax_group.pos_receipt_label,\n            });\n        }\n\n        // Subtotals.\n        if (!Object.keys(subtotals).length) {\n            subtotals[untaxed_amount_subtotal_label] = {\n                tax_groups: [],\n                tax_amount_currency: 0.0,\n                tax_amount: 0.0,\n                base_amount_currency: 0.0,\n                base_amount: 0.0,\n            };\n        }\n\n        const ordered_subtotals = Array.from(Object.entries(subtotals)).sort(\n            (a, b) => (subtotals_order[a[0]] || 0) - (subtotals_order[b[0]] || 0)\n        );\n        let accumulated_tax_amount_currency = 0.0;\n        let accumulated_tax_amount = 0.0;\n        for (const [subtotal_label, subtotal] of ordered_subtotals) {\n            subtotal.name = subtotal_label;\n            subtotal.base_amount_currency =\n                tax_totals_summary.base_amount_currency + accumulated_tax_amount_currency;\n            subtotal.base_amount = tax_totals_summary.base_amount + accumulated_tax_amount;\n            for (const tax_group of subtotal.tax_groups) {\n                subtotal.tax_amount_currency += tax_group.tax_amount_currency;\n                subtotal.tax_amount += tax_group.tax_amount;\n                accumulated_tax_amount_currency += tax_group.tax_amount_currency;\n                accumulated_tax_amount += tax_group.tax_amount;\n            }\n            tax_totals_summary.subtotals.push(subtotal);\n        }\n\n        // Cash rounding\n        const cash_rounding_lines = base_lines.filter(\n            (base_line) => base_line.special_type === \"cash_rounding\"\n        );\n        if (cash_rounding_lines.length) {\n            tax_totals_summary.cash_rounding_base_amount_currency = 0.0;\n            tax_totals_summary.cash_rounding_base_amount = 0.0;\n            cash_rounding_lines.forEach((base_line) => {\n                const tax_details = base_line.tax_details;\n                tax_totals_summary.cash_rounding_base_amount_currency +=\n                    tax_details.total_excluded_currency;\n                tax_totals_summary.cash_rounding_base_amount += tax_details.total_excluded;\n            });\n        } else if (cash_rounding !== null) {\n            const strategy = cash_rounding.strategy;\n            const cash_rounding_pd = cash_rounding.rounding;\n            const cash_rounding_method = cash_rounding.rounding_method;\n            const total_amount_currency =\n                tax_totals_summary.base_amount_currency + tax_totals_summary.tax_amount_currency;\n            const total_amount = tax_totals_summary.base_amount + tax_totals_summary.tax_amount;\n            const expected_total_amount_currency = roundPrecision(\n                total_amount_currency,\n                cash_rounding_pd,\n                cash_rounding_method\n            );\n            let cash_rounding_base_amount_currency =\n                expected_total_amount_currency - total_amount_currency;\n            const rate = total_amount ? Math.abs(total_amount_currency / total_amount) : 0.0;\n            let cash_rounding_base_amount = rate\n                ? roundPrecision(cash_rounding_base_amount_currency / rate, company_pd)\n                : 0.0;\n            if (!floatIsZero(cash_rounding_base_amount_currency, currency.decimal_places)) {\n                if (strategy === \"add_invoice_line\") {\n                    tax_totals_summary.cash_rounding_base_amount_currency =\n                        cash_rounding_base_amount_currency;\n                    tax_totals_summary.cash_rounding_base_amount = cash_rounding_base_amount;\n                    tax_totals_summary.base_amount_currency += cash_rounding_base_amount_currency;\n                    tax_totals_summary.base_amount += cash_rounding_base_amount;\n                    subtotals[untaxed_amount_subtotal_label].base_amount_currency +=\n                        cash_rounding_base_amount_currency;\n                    subtotals[untaxed_amount_subtotal_label].base_amount +=\n                        cash_rounding_base_amount;\n                } else if (strategy === \"biggest_tax\") {\n                    const all_subtotal_tax_group = tax_totals_summary.subtotals.flatMap(\n                        (subtotal) => subtotal.tax_groups.map((tax_group) => [subtotal, tax_group])\n                    );\n\n                    if (all_subtotal_tax_group.length) {\n                        const [max_subtotal, max_tax_group] = all_subtotal_tax_group.reduce(\n                            (a, b) => (b[1].tax_amount_currency > a[1].tax_amount_currency ? b : a)\n                        );\n\n                        max_tax_group.tax_amount_currency += cash_rounding_base_amount_currency;\n                        max_tax_group.tax_amount += cash_rounding_base_amount;\n                        max_subtotal.tax_amount_currency += cash_rounding_base_amount_currency;\n                        max_subtotal.tax_amount += cash_rounding_base_amount;\n                        tax_totals_summary.tax_amount_currency +=\n                            cash_rounding_base_amount_currency;\n                        tax_totals_summary.tax_amount += cash_rounding_base_amount;\n                    } else {\n                        // Failed to apply the cash rounding since there is no tax.\n                        cash_rounding_base_amount_currency = 0.0;\n                        cash_rounding_base_amount = 0.0;\n                    }\n                }\n            }\n        }\n\n        // Subtract the cash rounding from the untaxed amounts.\n        const cash_rounding_base_amount_currency =\n            tax_totals_summary.cash_rounding_base_amount_currency || 0.0;\n        const cash_rounding_base_amount = tax_totals_summary.cash_rounding_base_amount || 0.0;\n        tax_totals_summary.base_amount_currency -= cash_rounding_base_amount_currency;\n        tax_totals_summary.base_amount -= cash_rounding_base_amount;\n        for (const subtotal of tax_totals_summary.subtotals) {\n            subtotal.base_amount_currency -= cash_rounding_base_amount_currency;\n            subtotal.base_amount -= cash_rounding_base_amount;\n        }\n        encountered_base_amounts.add(\n            parseFloat(tax_totals_summary.base_amount_currency.toFixed(currency.decimal_places))\n        );\n        tax_totals_summary.same_tax_base = encountered_base_amounts.size === 1;\n\n        // Total amount.\n        tax_totals_summary.total_amount_currency =\n            tax_totals_summary.base_amount_currency +\n            tax_totals_summary.tax_amount_currency +\n            cash_rounding_base_amount_currency;\n        tax_totals_summary.total_amount =\n            tax_totals_summary.base_amount +\n            tax_totals_summary.tax_amount +\n            cash_rounding_base_amount;\n\n        return tax_totals_summary;\n    },\n\n    // -------------------------------------------------------------------------\n    // AGGREGATOR OF TAX DETAILS\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    aggregate_base_line_tax_details(base_line, grouping_function) {\n        const values_per_grouping_key = {};\n        const tax_details = base_line.tax_details;\n        const taxes_data = tax_details.taxes_data;\n        const manual_tax_amounts = base_line.manual_tax_amounts;\n\n        // If there are no taxes, we pass an empty object to the grouping function.\n        for (const tax_data of taxes_data.length !== 0 ? taxes_data : [null]) {\n            const current_manual_tax_amounts =\n                tax_data && manual_tax_amounts\n                    ? manual_tax_amounts[tax_data.tax.id.toString()] || {}\n                    : {};\n\n            let raw_grouping_key = grouping_function(base_line, tax_data);\n            let grouping_key;\n            if (\n                raw_grouping_key &&\n                typeof raw_grouping_key === \"object\" &&\n                \"raw_grouping_key\" in raw_grouping_key\n            ) {\n                // TODO: TO BE REMOVED IN MASTER (here for retro-compatibility)\n                // There is no FrozenDict in javascript.\n                // When the key is a record, it can't be jsonified so this is a trick to provide both the\n                // raw_grouping_key (to be jsonified) from the grouping_key (to be added to the values).\n                raw_grouping_key = raw_grouping_key.raw_grouping_key;\n                grouping_key = raw_grouping_key.grouping_key;\n\n                // Handle dictionary-like keys (converted to string in JS)\n                if (typeof grouping_key === \"object\") {\n                    grouping_key = JSON.stringify(grouping_key);\n                }\n            } else {\n                grouping_key = this.stringify_grouping_key(raw_grouping_key);\n            }\n\n            // Base amount.\n            if (!(grouping_key in values_per_grouping_key)) {\n                const values = {\n                    grouping_key: raw_grouping_key,\n                    taxes_data: [],\n                };\n                values_per_grouping_key[grouping_key] = values;\n\n                for (const suffix of [\"_currency\", \"\"]) {\n                    const excluded_rounded_field = `total_excluded${suffix}`;\n                    const excluded_delta_field = `delta_${excluded_rounded_field}`;\n                    const excluded_raw_field = `raw_${excluded_rounded_field}`;\n                    const excluded_target_field = `target_${excluded_rounded_field}`;\n                    const excluded_manual_field = `manual_${excluded_rounded_field}`;\n\n                    const excluded_rounded_amount =\n                        tax_details[excluded_rounded_field] + tax_details[excluded_delta_field];\n                    const excluded_raw_amount = tax_details[excluded_raw_field];\n\n                    values[excluded_rounded_field] = excluded_rounded_amount;\n                    values[excluded_raw_field] = excluded_raw_amount;\n\n                    let excluded_target_amount;\n                    if (base_line[excluded_manual_field] !== null) {\n                        excluded_target_amount = base_line[excluded_manual_field];\n                    } else if (suffix === \"\" && base_line.manual_total_excluded_currency !== null) {\n                        excluded_target_amount = excluded_rounded_amount;\n                    } else {\n                        excluded_target_amount = excluded_raw_amount;\n                    }\n                    values[excluded_target_field] = excluded_target_amount;\n\n                    const tax_base_rounded_field = `base_amount${suffix}`;\n                    const tax_base_raw_field = `raw_${tax_base_rounded_field}`;\n                    const tax_base_target_field = `target_${tax_base_rounded_field}`;\n\n                    if (tax_data) {\n                        values[tax_base_rounded_field] = tax_data[tax_base_rounded_field];\n                        values[tax_base_raw_field] = tax_data[tax_base_raw_field];\n\n                        if (tax_base_rounded_field in current_manual_tax_amounts) {\n                            values[tax_base_target_field] =\n                                current_manual_tax_amounts[tax_base_rounded_field];\n                        } else if (\n                            suffix === \"\" &&\n                            \"base_amount_currency\" in current_manual_tax_amounts\n                        ) {\n                            values[tax_base_target_field] = tax_data[tax_base_rounded_field];\n                        } else {\n                            values[tax_base_target_field] = tax_data[tax_base_raw_field];\n                        }\n                    } else {\n                        values[tax_base_rounded_field] = excluded_rounded_amount;\n                        values[tax_base_raw_field] = excluded_raw_amount;\n                        values[tax_base_target_field] = excluded_target_amount;\n                    }\n\n                    const tax_rounded_field = `tax_amount${suffix}`;\n                    const tax_raw_field = `raw_${tax_rounded_field}`;\n                    const tax_target_field = `target_${tax_rounded_field}`;\n\n                    values[tax_rounded_field] = 0.0;\n                    values[tax_raw_field] = 0.0;\n                    values[tax_target_field] = 0.0;\n                }\n            }\n\n            // Tax amount.\n            if (tax_data) {\n                const reverse_charge_sign = tax_data.is_reverse_charge ? -1 : 1;\n                const values = values_per_grouping_key[grouping_key];\n                for (const suffix of [\"_currency\", \"\"]) {\n                    const tax_rounded_field = `tax_amount${suffix}`;\n                    const tax_raw_field = `raw_${tax_rounded_field}`;\n                    const tax_target_field = `target_${tax_rounded_field}`;\n\n                    values[tax_rounded_field] += tax_data[tax_rounded_field];\n                    values[tax_raw_field] += tax_data[tax_raw_field];\n\n                    if (tax_rounded_field in current_manual_tax_amounts) {\n                        values[tax_target_field] +=\n                            reverse_charge_sign * current_manual_tax_amounts[tax_rounded_field];\n                    } else if (\n                        suffix === \"\" &&\n                        \"tax_amount_currency\" in current_manual_tax_amounts\n                    ) {\n                        values[tax_target_field] = tax_data[tax_rounded_field];\n                    } else {\n                        values[tax_target_field] += tax_data[tax_raw_field];\n                    }\n                }\n                values.taxes_data.push(tax_data);\n            }\n        }\n        return values_per_grouping_key;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    aggregate_base_lines_tax_details(base_lines, grouping_function) {\n        return base_lines.map((base_line) => [\n            base_line,\n            this.aggregate_base_line_tax_details(base_line, grouping_function),\n        ]);\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    aggregate_base_lines_aggregated_values(base_lines_aggregated_values) {\n        const default_float_fields = new Set();\n        for (const prefix of [\"\", \"raw_\", \"target_\"]) {\n            for (const suffix of [\"_currency\", \"\"]) {\n                for (const field of [\"base_amount\", \"tax_amount\", \"total_excluded\"]) {\n                    default_float_fields.add(`${prefix}${field}${suffix}`);\n                }\n            }\n        }\n\n        const values_per_grouping_key = {};\n        for (const [base_line, aggregated_values] of base_lines_aggregated_values) {\n            for (const [raw_grouping_key, values] of Object.entries(aggregated_values)) {\n                const grouping_key = values.grouping_key;\n\n                if (!(raw_grouping_key in values_per_grouping_key)) {\n                    const initial_values = (values_per_grouping_key[raw_grouping_key] = {\n                        base_line_x_taxes_data: [],\n                        grouping_key: grouping_key,\n                    });\n                    default_float_fields.forEach((field) => {\n                        initial_values[field] = 0.0;\n                    });\n                }\n                const agg_values = values_per_grouping_key[raw_grouping_key];\n                default_float_fields.forEach((field) => {\n                    agg_values[field] += values[field];\n                });\n                agg_values.base_line_x_taxes_data.push([base_line, values.taxes_data]);\n            }\n        }\n        return values_per_grouping_key;\n    },\n\n    // -------------------------------------------------------------------------\n    // ADVANCED LINES MANIPULATION HELPERS\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    can_be_discounted(tax) {\n        return ![\"fixed\", \"code\"].includes(tax.amount_type);\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    merge_tax_details(tax_details_1, tax_details_2) {\n        const results = {};\n        for (const prefix of [\"raw_\", \"\"]) {\n            for (const field of [\"total_excluded\", \"total_included\"]) {\n                for (const suffix of [\"_currency\", \"\"]) {\n                    const key = `${prefix}${field}${suffix}`;\n                    results[key] = tax_details_1[key] + tax_details_2[key];\n                }\n            }\n        }\n        for (const suffix of [\"_currency\", \"\"]) {\n            const field = `delta_total_excluded${suffix}`;\n            results[field] = tax_details_1[field] + tax_details_2[field];\n        }\n\n        const agg_taxes_data = {};\n        for (const tax_details of [tax_details_1, tax_details_2]) {\n            for (const tax_data of tax_details.taxes_data) {\n                const tax = tax_data.tax;\n                const tax_id_str = tax.id.toString();\n                if (tax_id_str in agg_taxes_data) {\n                    const agg_tax_data = agg_taxes_data[tax_id_str];\n                    for (const prefix of [\"raw_\", \"\"]) {\n                        for (const suffix of [\"_currency\", \"\"]) {\n                            for (const field of [\"base_amount\", \"tax_amount\"]) {\n                                const field_with_prefix = `${prefix}${field}${suffix}`;\n                                agg_tax_data[field_with_prefix] += tax_data[field_with_prefix];\n                            }\n                        }\n                    }\n                } else {\n                    agg_taxes_data[tax_id_str] = { ...tax_data };\n                }\n            }\n        }\n        results.taxes_data = Object.values(agg_taxes_data);\n\n        // In case there is some taxes that are in tax_details_1 but not on tax_details_2,\n        // we have to shift manually the base amount. It happens with fixed taxes in which the base\n        // is meaningless but still used in the computations.\n        const taxes_data_in_2 = new Set(tax_details_2.taxes_data.map((td) => td.tax.id));\n        const not_discountable_taxes_data = new Set(\n            tax_details_1.taxes_data\n                .filter((td) => !taxes_data_in_2.has(td.tax.id))\n                .map((td) => td.tax.id)\n        );\n        for (const tax_data of results.taxes_data) {\n            if (not_discountable_taxes_data.has(tax_data.tax.id)) {\n                for (const suffix of [\"_currency\", \"\"]) {\n                    for (const prefix of [\"raw_\", \"\"]) {\n                        tax_data[`${prefix}base_amount${suffix}`] +=\n                            tax_details_2[`${prefix}total_excluded${suffix}`];\n                    }\n                    tax_data[`base_amount${suffix}`] +=\n                        tax_details_2[`delta_total_excluded${suffix}`];\n                }\n            }\n        }\n\n        return results;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    fix_base_lines_tax_details_on_manual_tax_amounts(\n        base_lines,\n        company,\n        { filter_function = null } = {}\n    ) {\n        for (const base_line of base_lines) {\n            const tax_details = base_line.tax_details;\n            const taxes_data = tax_details.taxes_data;\n            if (!taxes_data.length) {\n                continue;\n            }\n\n            base_line.manual_total_excluded_currency =\n                tax_details.total_excluded_currency + tax_details.delta_total_excluded_currency;\n            base_line.manual_total_excluded =\n                tax_details.total_excluded + tax_details.delta_total_excluded;\n            base_line.manual_tax_amounts = {};\n            for (const tax_data of taxes_data) {\n                if (tax_data.is_reverse_charge) {\n                    continue;\n                }\n                const tax = tax_data.tax;\n                const tax_id_str = tax.id.toString();\n                base_line.manual_tax_amounts[tax_id_str] = {};\n                if (filter_function && !filter_function(base_line, tax_data)) {\n                    continue;\n                }\n\n                base_line.manual_tax_amounts[tax_id_str] = {\n                    tax_amount_currency: tax_data.tax_amount_currency,\n                    tax_amount: tax_data.tax_amount,\n                    base_amount_currency: tax_data.base_amount_currency,\n                    base_amount: tax_data.base_amount,\n                };\n            }\n        }\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    split_tax_data(base_line, tax_data, company, target_factors) {\n        const currency = base_line.currency_id;\n\n        const factors = this.normalize_target_factors(target_factors);\n\n        const new_taxes_data = [];\n\n        // Distribution of raw amounts.\n        for (const index_factor of factors) {\n            const factor = index_factor[1];\n            new_taxes_data.push({\n                ...tax_data,\n                raw_tax_amount_currency: factor * tax_data.raw_tax_amount_currency,\n                raw_tax_amount: factor * tax_data.raw_tax_amount,\n                raw_base_amount_currency: factor * tax_data.raw_base_amount_currency,\n                raw_base_amount: factor * tax_data.raw_base_amount,\n            });\n        }\n\n        // Distribution of rounded amounts.\n        const new_target_factors = new_taxes_data.map((new_tax_data, index) => ({\n            factor: target_factors[index].factor,\n            tax_data: new_tax_data,\n        }));\n\n        for (const [delta_currency_indicator, delta_currency] of [\n            [\"_currency\", currency],\n            [\"\", company.currency_id],\n        ]) {\n            for (const prefix of [\"tax\", \"base\"]) {\n                const field = `${prefix}_amount${delta_currency_indicator}`;\n                const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                    delta_currency.decimal_places,\n                    tax_data[field],\n                    new_target_factors\n                );\n                for (let i = 0; i < new_target_factors.length; i++) {\n                    const target_factor = new_target_factors[i];\n                    const amount_to_distribute = amounts_to_distribute[i];\n                    const new_tax_data = target_factor.tax_data;\n                    new_tax_data[field] = amount_to_distribute;\n                }\n            }\n        }\n        return new_taxes_data;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    split_tax_details(base_line, company, target_factors) {\n        const currency = base_line.currency_id;\n        const tax_details = base_line.tax_details;\n\n        const factors = this._normalize_target_factors(target_factors);\n\n        const new_tax_details_list = [];\n\n        // Distribution of raw amounts.\n        for (const index_factor of factors) {\n            const factor = index_factor[1];\n            new_tax_details_list.push({\n                raw_total_excluded_currency: factor * tax_details.raw_total_excluded_currency,\n                raw_total_excluded: factor * tax_details.raw_total_excluded,\n                raw_total_included_currency: factor * tax_details.raw_total_included_currency,\n                raw_total_included: factor * tax_details.raw_total_included,\n                delta_total_excluded_currency: 0.0,\n                delta_total_excluded: 0.0,\n                taxes_data: [],\n            });\n        }\n\n        // Manage 'taxes_data'.\n        for (const tax_data of tax_details.taxes_data) {\n            const new_taxes_data = this.split_tax_data(\n                base_line,\n                tax_data,\n                company,\n                target_factors\n            );\n            for (let i = 0; i < new_tax_details_list.length; i++) {\n                const new_tax_details = new_tax_details_list[i];\n                const new_tax_data = new_taxes_data[i];\n                new_tax_details.taxes_data.push(new_tax_data);\n            }\n        }\n\n        // Distribution of rounded amounts.\n        for (const [delta_currency_indicator, delta_currency] of [\n            [\"_currency\", currency],\n            [\"\", company.currency_id],\n        ]) {\n            const new_target_factors = new_tax_details_list.map((new_tax_details) => ({\n                factor: new_tax_details[`raw_total_excluded${delta_currency_indicator}`],\n                tax_details: new_tax_details,\n            }));\n            const field = `total_excluded${delta_currency_indicator}`;\n            const delta_amount = tax_details[field];\n            const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                delta_currency.decimal_places,\n                delta_amount,\n                new_target_factors\n            );\n            for (let i = 0; i < new_target_factors.length; i++) {\n                const target_factor = new_target_factors[i];\n                const amount_to_distribute = amounts_to_distribute[i];\n                const new_tax_details = target_factor.tax_details;\n                new_tax_details[field] = amount_to_distribute;\n            }\n        }\n\n        // Manage 'total_included'.\n        for (const new_tax_details of new_tax_details_list) {\n            for (const delta_currency_indicator of [\"_currency\", \"\"]) {\n                new_tax_details[`total_included${delta_currency_indicator}`] =\n                    new_tax_details[`total_excluded${delta_currency_indicator}`] +\n                    new_tax_details.taxes_data.reduce(\n                        (sum, new_tax_data) =>\n                            sum + new_tax_data[`tax_amount${delta_currency_indicator}`],\n                        0\n                    );\n            }\n        }\n        return new_tax_details_list;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    split_base_line(base_line, company, target_factors, { populate_function = null } = {}) {\n        const factors = this.normalize_target_factors(target_factors);\n\n        // Split 'tax_details'.\n        const new_tax_details_list = this.split_tax_details(base_line, company, target_factors);\n\n        // Split 'base_line'.\n        const new_base_lines = factors.map((x) => null);\n        for (let i = 0; i < factors.length; i++) {\n            const index = factors[i][0];\n            const factor = factors[i][1];\n            const new_tax_details = new_tax_details_list[i];\n            const target_factor = target_factors[i];\n\n            const kwargs = {\n                price_unit: factor * base_line.price_unit,\n                tax_details: new_tax_details,\n            };\n\n            if (populate_function) {\n                populate_function(base_line, target_factor, kwargs);\n            }\n\n            new_base_lines[index] = this.prepare_base_line_for_taxes_computation(base_line, kwargs);\n        }\n        return new_base_lines;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     * DEPRECATED: TO BE REMOVED IN MASTER\n     */\n    compute_subset_base_lines_total(base_lines, company) {\n        let base_amount_currency = 0.0;\n        let tax_amount_currency = 0.0;\n        let base_amount = 0.0;\n        let tax_amount = 0.0;\n        const tax_amounts_mapping = {};\n        let raw_total_included_currency = 0.0;\n        let raw_total_included = 0.0;\n        for (const base_line of base_lines) {\n            const tax_details = base_line.tax_details;\n            base_amount_currency +=\n                tax_details.total_excluded_currency + tax_details.delta_total_excluded_currency;\n            base_amount += tax_details.total_excluded + tax_details.delta_total_excluded;\n            raw_total_included_currency += tax_details.raw_total_excluded_currency;\n            raw_total_included += tax_details.raw_total_excluded;\n            for (const tax_data of tax_details.taxes_data) {\n                const tax = tax_data.tax;\n                if (!this.can_be_discounted(tax)) {\n                    continue;\n                }\n\n                const tax_id_str = tax.id.toString();\n                if (!(tax_id_str in tax_amounts_mapping)) {\n                    tax_amounts_mapping[tax_id_str] = {\n                        tax_amount_currency: 0.0,\n                        tax_amount: 0.0,\n                    };\n                }\n\n                tax_amount_currency += tax_data.tax_amount_currency;\n                tax_amount += tax_data.tax_amount;\n                tax_amounts_mapping[tax_id_str].tax_amount_currency += tax_data.tax_amount_currency;\n                tax_amounts_mapping[tax_id_str].tax_amount += tax_data.tax_amount;\n                raw_total_included_currency += tax_data.raw_tax_amount_currency;\n                raw_total_included += tax_data.raw_tax_amount;\n            }\n        }\n        return {\n            base_amount_currency: base_amount_currency,\n            tax_amount_currency: tax_amount_currency,\n            base_amount: base_amount,\n            tax_amount: tax_amount,\n            tax_amounts_mapping: tax_amounts_mapping,\n            raw_total_included_currency: raw_total_included_currency,\n            raw_total_included: raw_total_included,\n            rate: raw_total_included ? raw_total_included_currency / raw_total_included : 0.0,\n        };\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    reduce_base_lines_with_grouping_function(\n        base_lines,\n        { grouping_function = null, aggregate_function = null, computation_key = null } = {}\n    ) {\n        const base_line_map = {};\n        for (const base_line of base_lines) {\n            const price_unit_after_discount =\n                base_line.price_unit * (1 - base_line.discount / 100.0);\n            const new_base_line = this.prepare_base_line_for_taxes_computation(base_line, {\n                price_unit: base_line.quantity * price_unit_after_discount,\n                quantity: 1.0,\n                discount: 0.0,\n            });\n            const raw_grouping_key = {\n                tax_ids: new_base_line.tax_ids.map((tax) => tax.id),\n                computation_key: base_line.computation_key,\n            };\n            const grouping_key = {\n                tax_ids: new_base_line.tax_ids.map((tax) => tax),\n                computation_key: base_line.computation_key,\n            };\n            if (grouping_function) {\n                const generated_grouping_key = grouping_function(new_base_line);\n\n                // There is no FrozenDict in javascript.\n                // When the key is a record, it can't be jsonified so this is a trick to provide both the\n                // raw_grouping_key (to be jsonified) from the grouping_key (to be added to the values).\n                if (\"raw_grouping_key\" in generated_grouping_key) {\n                    Object.assign(raw_grouping_key, generated_grouping_key.raw_grouping_key);\n                    Object.assign(grouping_key, generated_grouping_key.grouping_key);\n                } else {\n                    Object.assign(raw_grouping_key, generated_grouping_key);\n                    Object.assign(grouping_key, generated_grouping_key);\n                }\n            }\n\n            const grouping_key_json = JSON.stringify(raw_grouping_key);\n            let target_base_line = base_line_map[grouping_key_json];\n            if (target_base_line) {\n                target_base_line.price_unit += new_base_line.price_unit;\n                target_base_line.tax_details = this.merge_tax_details(\n                    target_base_line.tax_details,\n                    base_line.tax_details\n                );\n                if (aggregate_function) {\n                    aggregate_function(target_base_line, base_line);\n                }\n            } else {\n                target_base_line = this.prepare_base_line_for_taxes_computation(new_base_line, {\n                    ...grouping_key,\n                    computation_key: computation_key,\n                    tax_details: {\n                        ...base_line.tax_details,\n                        taxes_data: base_line.tax_details.taxes_data.map((tax_data) =>\n                            Object.assign({}, tax_data)\n                        ),\n                    },\n                });\n                base_line_map[grouping_key_json] = target_base_line;\n                if (aggregate_function) {\n                    aggregate_function(target_base_line, base_line);\n                }\n            }\n        }\n\n        // Remove zero lines.\n        const reduced_base_lines = Object.values(base_line_map).filter(\n            (base_line) => !floatIsZero(base_line.price_unit, base_line.currency_id.decimal_places)\n        );\n        return reduced_base_lines;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     * DEPRECATED: TO BE REMOVED IN MASTER\n     */\n    apply_base_lines_manual_amounts_to_reach(\n        base_lines,\n        company,\n        target_base_amount_currency,\n        target_base_amount,\n        target_tax_amounts_mapping\n    ) {\n        const currency = base_lines[0].currency_id;\n\n        // Smooth distribution of the delta accross the base line, starting at the biggest one.\n        const sorted_base_lines = base_lines.sort((base_line_1, base_line_2) => {\n            const key_1 = [\n                Boolean(base_line_1.special_type),\n                -base_line_1.tax_details.total_excluded_currency,\n            ];\n            const key_2 = [\n                Boolean(base_line_2.special_type),\n                -base_line_2.tax_details.total_excluded_currency,\n            ];\n\n            if (key_1[0] !== key_2[0]) {\n                return key_1[0] - key_2[0];\n            }\n            return key_1[1] - key_2[1];\n        });\n        const base_lines_totals = this.compute_subset_base_lines_total(base_lines, company);\n        for (const [delta_suffix, delta_target_base_amount, delta_currency] of [\n            [\"_currency\", target_base_amount_currency, currency],\n            [\"\", target_base_amount, company.currency_id],\n        ]) {\n            const target_factors = sorted_base_lines.map((base_line) => ({\n                factor: Math.abs(\n                    (base_line.tax_details.total_excluded_currency +\n                        base_line.tax_details.delta_total_excluded_currency) /\n                        base_lines_totals.base_amount_currency\n                ),\n                base_line: base_line,\n            }));\n            const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                delta_currency.decimal_places,\n                delta_target_base_amount - base_lines_totals[`base_amount${delta_suffix}`],\n                target_factors\n            );\n            for (let i = 0; i < target_factors.length; i++) {\n                const target_factor = target_factors[i];\n                const amount_to_distribute = amounts_to_distribute[i];\n                const base_line = target_factor.base_line;\n                const tax_details = base_line.tax_details;\n                const taxes_data = tax_details.taxes_data;\n                if (delta_suffix === \"_currency\") {\n                    base_line.price_unit +=\n                        amount_to_distribute / Math.abs(base_line.quantity || 1.0);\n                }\n                if (!taxes_data.length) {\n                    continue;\n                }\n\n                const first_batch = taxes_data[0].batch;\n                for (const tax_data of taxes_data) {\n                    const tax = tax_data.tax;\n                    if (first_batch.includes(tax)) {\n                        tax_data[`base_amount${delta_suffix}`] += amount_to_distribute;\n                    } else {\n                        break;\n                    }\n                }\n            }\n        }\n\n        for (const [tax_id_str, tax_amounts] of Object.entries(target_tax_amounts_mapping)) {\n            for (const [delta_suffix, delta_target_tax_amount, delta_currency] of [\n                [\"_currency\", tax_amounts.tax_amount_currency, currency],\n                [\"\", tax_amounts.tax_amount, company.currency_id],\n            ]) {\n                const current_tax_amounts = base_lines_totals.tax_amounts_mapping[tax_id_str];\n                if (!current_tax_amounts.tax_amount_currency) {\n                    continue;\n                }\n\n                const target_factors = [];\n                for (const base_line of sorted_base_lines) {\n                    for (const tax_data of base_line.tax_details.taxes_data) {\n                        if (tax_data.tax.id.toString() === tax_id_str) {\n                            target_factors.push({\n                                factor: Math.abs(\n                                    tax_data.tax_amount_currency /\n                                        current_tax_amounts.tax_amount_currency\n                                ),\n                                tax_data: tax_data,\n                            });\n                        }\n                    }\n                }\n                const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                    delta_currency.decimal_places,\n                    delta_target_tax_amount - current_tax_amounts[`tax_amount${delta_suffix}`],\n                    target_factors\n                );\n                for (let i = 0; i < target_factors.length; i++) {\n                    const target_factor = target_factors[i];\n                    const amount_to_distribute = amounts_to_distribute[i];\n                    const tax_data = target_factor.tax_data;\n                    tax_data[`tax_amount${delta_suffix}`] += amount_to_distribute;\n                }\n            }\n        }\n\n        this.fix_base_lines_tax_details_on_manual_tax_amounts(base_lines, company);\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    reduce_base_lines_to_target_amount(\n        base_lines,\n        company,\n        amount_type,\n        amount,\n        { computation_key = null, grouping_function = null, aggregate_function = null } = {}\n    ) {\n        if (!base_lines.length) {\n            return [];\n        }\n\n        const currency = base_lines[0].currency_id;\n        const rate = base_lines[0].rate;\n\n        // Compute the current total amount of the base lines.\n        function grouping_function_total(base_line, tax_data) {\n            return true;\n        }\n\n        let base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            base_lines,\n            grouping_function_total\n        );\n        let values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n        const total_amount_currency = Object.values(values_per_grouping_key).reduce(\n            (acc, values) => acc + values.total_excluded_currency + values.tax_amount_currency,\n            0\n        );\n        const total_amount = Object.values(values_per_grouping_key).reduce(\n            (acc, values) => acc + values.total_excluded + values.tax_amount,\n            0\n        );\n\n        // Compute the current total tax amount per tax.\n        function grouping_function_tax(base_line, tax_data) {\n            return tax_data ? tax_data.tax.id.toString() : null;\n        }\n\n        base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            base_lines,\n            grouping_function_tax\n        );\n        values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n        const tax_amounts_per_tax = {};\n        for (const [grouping_key, values] of Object.entries(values_per_grouping_key)) {\n            if (!grouping_key) {\n                continue;\n            }\n\n            tax_amounts_per_tax[grouping_key] = {\n                tax_amount_currency: values.tax_amount_currency,\n                tax_amount: values.tax_amount,\n                base_amount_currency: values.base_amount_currency,\n                base_amount: values.base_amount,\n            };\n        }\n\n        // Turn the 'amount_type' / 'amount' into a percentage and the total amounts to be reached\n        // from the base lines.\n        const sign = amount < 0.0 ? -1 : 1;\n        const signed_amount = sign * amount;\n        let percentage, expected_total_amount_currency, expected_total_amount;\n        if (amount_type === \"fixed\") {\n            percentage = total_amount_currency ? signed_amount / total_amount_currency : 0.0;\n            expected_total_amount_currency = roundPrecision(amount, currency.rounding);\n            expected_total_amount = rate\n                ? roundPrecision(\n                      expected_total_amount_currency / rate,\n                      company.currency_id.rounding\n                  )\n                : 0.0;\n        } else {\n            percentage = signed_amount / 100.0;\n            expected_total_amount_currency = roundPrecision(\n                total_amount_currency * sign * percentage,\n                currency.rounding\n            );\n            expected_total_amount = roundPrecision(\n                total_amount * sign * percentage,\n                company.currency_id.rounding\n            );\n        }\n\n        // Compute the expected amounts.\n        const expected_tax_amounts = {};\n        for (const [grouping_key, values] of Object.entries(tax_amounts_per_tax)) {\n            expected_tax_amounts[grouping_key] = {\n                tax_amount_currency: roundPrecision(\n                    values.tax_amount_currency * sign * percentage,\n                    currency.rounding\n                ),\n                tax_amount: roundPrecision(\n                    values.tax_amount * sign * percentage,\n                    company.currency_id.rounding\n                ),\n                base_amount_currency: roundPrecision(\n                    values.base_amount_currency * sign * percentage,\n                    currency.rounding\n                ),\n                base_amount: roundPrecision(\n                    values.base_amount * sign * percentage,\n                    company.currency_id.rounding\n                ),\n            };\n        }\n        const expected_base_amount_currency =\n            expected_total_amount_currency -\n            Object.values(expected_tax_amounts).reduce((acc, v) => acc + v.tax_amount_currency, 0);\n        const expected_base_amount =\n            expected_total_amount -\n            Object.values(expected_tax_amounts).reduce((acc, v) => acc + v.tax_amount, 0);\n\n        // Reduce the base lines to minimize the number of lines.\n        const reduced_base_lines = this.reduce_base_lines_with_grouping_function(base_lines, {\n            grouping_function: grouping_function,\n            aggregate_function: aggregate_function,\n            computation_key: computation_key,\n        });\n        if (!reduced_base_lines.length) {\n            return [];\n        }\n\n        // Reduce the unit price to approach the target amount.\n        const new_base_lines = reduced_base_lines.map((base_line) =>\n            this.prepare_base_line_for_taxes_computation(base_line, {\n                price_unit: base_line.price_unit * sign * percentage,\n                computation_key: computation_key,\n            })\n        );\n        this.add_tax_details_in_base_lines(new_base_lines, company);\n        this.round_base_lines_tax_details(new_base_lines, company);\n\n        // Smooth distribution of the delta tax/base amounts.\n        const sorted_base_lines = new_base_lines.sort((base_line_1, base_line_2) => {\n            const key_1 = [\n                Boolean(base_line_1.special_type),\n                -base_line_1.tax_details.total_excluded_currency,\n            ];\n            const key_2 = [\n                Boolean(base_line_2.special_type),\n                -base_line_2.tax_details.total_excluded_currency,\n            ];\n\n            if (key_1[0] !== key_2[0]) {\n                return key_1[0] - key_2[0];\n            }\n            return key_1[1] - key_2[1];\n        });\n        base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            new_base_lines,\n            grouping_function_tax\n        );\n        values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n        const current_tax_amounts_per_tax = {};\n        for (const [grouping_key, values] of Object.entries(values_per_grouping_key)) {\n            if (!grouping_key) {\n                continue;\n            }\n            current_tax_amounts_per_tax[grouping_key] = {\n                tax_amount_currency: values.tax_amount_currency,\n                tax_amount: values.tax_amount,\n                base_amount_currency: values.base_amount_currency,\n                base_amount: values.base_amount,\n            };\n        }\n        for (const [tax_id_str, tax_amounts] of Object.entries(current_tax_amounts_per_tax)) {\n            const tax_amount_currency = tax_amounts.tax_amount_currency;\n            if (!tax_amount_currency) {\n                continue;\n            }\n\n            for (const [delta_suffix, delta_tax_amount, delta_base_amount, delta_currency] of [\n                [\n                    \"_currency\",\n                    expected_tax_amounts[tax_id_str].tax_amount_currency -\n                        tax_amounts.tax_amount_currency,\n                    expected_tax_amounts[tax_id_str].base_amount_currency -\n                        tax_amounts.base_amount_currency,\n                    currency,\n                ],\n                [\n                    \"\",\n                    expected_tax_amounts[tax_id_str].tax_amount - tax_amounts.tax_amount,\n                    expected_tax_amounts[tax_id_str].base_amount - tax_amounts.base_amount,\n                    company.currency_id,\n                ],\n            ]) {\n                // Tax amount.\n                const tax_amount_currency = tax_amounts.tax_amount_currency;\n                if (tax_amount_currency) {\n                    const target_factors = [];\n                    for (const base_line of sorted_base_lines) {\n                        for (const tax_data of base_line.tax_details.taxes_data) {\n                            if (tax_data.tax.id.toString() === tax_id_str) {\n                                target_factors.push({\n                                    factor: Math.abs(\n                                        tax_data.tax_amount_currency / tax_amount_currency\n                                    ),\n                                    base_line: base_line,\n                                    tax_data: tax_data,\n                                });\n                            }\n                        }\n                    }\n                    const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                        delta_currency.decimal_places,\n                        delta_tax_amount,\n                        target_factors\n                    );\n                    for (const [i, target_factor] of target_factors.entries()) {\n                        const amount_to_distribute = amounts_to_distribute[i];\n                        target_factor.tax_data[`tax_amount${delta_suffix}`] += amount_to_distribute;\n                    }\n                }\n\n                // Base amount.\n                const base_amount_currency = tax_amounts.base_amount_currency;\n                if (base_amount_currency) {\n                    const target_factors = [];\n                    for (const base_line of sorted_base_lines) {\n                        for (const tax_data of base_line.tax_details.taxes_data) {\n                            if (tax_data.tax.id.toString() === tax_id_str) {\n                                target_factors.push({\n                                    factor: Math.abs(\n                                        tax_data.base_amount_currency / base_amount_currency\n                                    ),\n                                    base_line: base_line,\n                                    tax_data: tax_data,\n                                });\n                            }\n                        }\n                    }\n                    const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                        delta_currency.decimal_places,\n                        delta_base_amount,\n                        target_factors\n                    );\n                    for (const [i, target_factor] of target_factors.entries()) {\n                        const amount_to_distribute = amounts_to_distribute[i];\n                        target_factor.tax_data[`base_amount${delta_suffix}`] +=\n                            amount_to_distribute;\n                    }\n                }\n            }\n        }\n\n        base_lines_aggregated_values = this.aggregate_base_lines_tax_details(\n            new_base_lines,\n            grouping_function_total\n        );\n        values_per_grouping_key = this.aggregate_base_lines_aggregated_values(\n            base_lines_aggregated_values\n        );\n        const current_base_amount_currency = Object.values(values_per_grouping_key).reduce(\n            (acc, values) => acc + values.total_excluded_currency,\n            0\n        );\n        const current_base_amount = Object.values(values_per_grouping_key).reduce(\n            (acc, values) => acc + values.total_excluded,\n            0\n        );\n        for (const [delta_suffix, delta_base_amount, delta_currency] of [\n            [\"_currency\", expected_base_amount_currency - current_base_amount_currency, currency],\n            [\"\", expected_base_amount - current_base_amount, company.currency_id],\n        ]) {\n            const target_factors = sorted_base_lines.map((base_line) => ({\n                factor: Math.abs(\n                    (base_line.tax_details.total_excluded_currency +\n                        base_line.tax_details.delta_total_excluded_currency) /\n                        current_base_amount_currency\n                ),\n                base_line: base_line,\n            }));\n            const amounts_to_distribute = this.distribute_delta_amount_smoothly(\n                delta_currency.decimal_places,\n                delta_base_amount,\n                target_factors\n            );\n            for (const [i, target_factor] of target_factors.entries()) {\n                const base_line = target_factor.base_line;\n                const amount_to_distribute = amounts_to_distribute[i];\n                const tax_details = base_line.tax_details;\n                tax_details[`delta_total_excluded${delta_suffix}`] += amount_to_distribute;\n                if (delta_suffix === \"_currency\") {\n                    base_line.price_unit += amount_to_distribute;\n                }\n            }\n        }\n        return new_base_lines;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    partition_base_lines_taxes(base_lines, partition_function) {\n        let has_taxes_to_exclude = false;\n        const base_lines_partition_taxes = [];\n        for (const base_line of base_lines) {\n            const tax_details = base_line.tax_details;\n            const taxes_data = tax_details.taxes_data;\n            const taxes_to_keep = [];\n            const taxes_to_exclude = [];\n            for (const tax_data of taxes_data) {\n                const tax = tax_data.tax;\n                if (partition_function(base_line, tax_data)) {\n                    taxes_to_keep.push(tax);\n                } else {\n                    taxes_to_exclude.push(tax);\n                }\n            }\n            if (taxes_to_exclude.length > 0) {\n                has_taxes_to_exclude = true;\n            }\n            base_lines_partition_taxes.push([base_line, taxes_to_keep, taxes_to_exclude]);\n        }\n        return [base_lines_partition_taxes, has_taxes_to_exclude];\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    prepare_discountable_base_lines(base_lines, company, { exclude_function = null } = {}) {\n        function dispatch_exclude_function(base_line, tax_data) {\n            return (\n                !this.can_be_discounted(tax_data.tax) ||\n                (exclude_function && exclude_function(base_line, tax_data))\n            );\n        }\n\n        return this.dispatch_taxes_into_new_base_lines(\n            base_lines,\n            company,\n            dispatch_exclude_function.bind(this)\n        );\n    },\n\n    // -------------------------------------------------------------------------\n    // GLOBAL DISCOUNT\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    prepare_global_discount_lines(\n        base_lines,\n        company,\n        amount_type,\n        amount,\n        { computation_key = \"global_discount\", grouping_function = null } = {}\n    ) {\n        const discountable_base_lines = this.prepare_discountable_base_lines(base_lines, company);\n        const new_base_lines = this.reduce_base_lines_to_target_amount(\n            discountable_base_lines,\n            company,\n            amount_type,\n            -amount,\n            { computation_key: computation_key, grouping_function: grouping_function }\n        );\n        this.fix_base_lines_tax_details_on_manual_tax_amounts(new_base_lines, company);\n        return new_base_lines;\n    },\n\n    // -------------------------------------------------------------------------\n    // DOWN PAYMENT\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    prepare_base_lines_for_down_payment(base_lines, company, { exclude_function = null } = {}) {\n        function dispatch_exclude_function(base_line, tax_data) {\n            return (\n                !this.can_be_discounted(tax_data.tax) ||\n                (exclude_function && exclude_function(base_line, tax_data))\n            );\n        }\n\n        const new_base_lines = this.dispatch_taxes_into_new_base_lines(\n            base_lines,\n            company,\n            dispatch_exclude_function.bind(this)\n        );\n        return new_base_lines.concat(\n            this.turn_removed_taxes_into_new_base_lines(new_base_lines, company)\n        );\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    prepare_down_payment_lines(\n        base_lines,\n        company,\n        amount_type,\n        amount,\n        { computation_key = \"down_payment\", grouping_function = null } = {}\n    ) {\n        const base_lines_for_dp = this.prepare_base_lines_for_down_payment(base_lines, company);\n        const new_base_lines = this.reduce_base_lines_to_target_amount(\n            base_lines_for_dp,\n            company,\n            amount_type,\n            amount,\n            { computation_key: computation_key, grouping_function: grouping_function }\n        );\n        this.fix_base_lines_tax_details_on_manual_tax_amounts(new_base_lines, company);\n        return new_base_lines;\n    },\n\n    // -------------------------------------------------------------------------\n    // DISPATCHING OF LINES\n    // -------------------------------------------------------------------------\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    dispatch_taxes_into_new_base_lines(base_lines, company, exclude_function) {\n        function partition_function(base_line, tax_data) {\n            return !exclude_function(base_line, tax_data);\n        }\n\n        const base_lines_partition_taxes = this.partition_base_lines_taxes(\n            base_lines,\n            partition_function\n        )[0];\n\n        const new_base_lines_list = base_lines.map(() => []);\n        const to_process = [];\n        base_lines_partition_taxes.forEach(\n            ([base_line, taxes_to_keep, taxes_to_exclude], index) => {\n                to_process.push([index, base_line, taxes_to_exclude]);\n            }\n        );\n        while (to_process.length) {\n            const [index, base_line, taxes_to_exclude] = to_process.shift();\n\n            const tax_details = base_line.tax_details;\n            const taxes_data = tax_details.taxes_data;\n\n            // Get the index of the next 'tax_data' to exclude.\n            let next_split_index = null;\n            for (let i = 0; i < taxes_data.length; i++) {\n                if (taxes_to_exclude.includes(taxes_data[i].tax)) {\n                    next_split_index = i;\n                    break;\n                }\n            }\n\n            if (next_split_index === null) {\n                new_base_lines_list[index].push({ ...base_line });\n                continue;\n            }\n\n            const common_taxes_data = taxes_data.slice(0, next_split_index);\n            const tax_data_to_remove = taxes_data[next_split_index];\n            const remaining_taxes_data = taxes_data.slice(next_split_index + 1);\n\n            // Split 'tax_details'.\n            const first_tax_details = {\n                raw_total_excluded_currency: tax_details.raw_total_excluded_currency,\n                raw_total_excluded: tax_details.raw_total_excluded,\n                total_excluded_currency: tax_details.total_excluded_currency,\n                total_excluded: tax_details.total_excluded,\n                delta_total_excluded_currency: tax_details.delta_total_excluded_currency,\n                delta_total_excluded: tax_details.delta_total_excluded,\n                taxes_data: common_taxes_data,\n            };\n            first_tax_details.raw_total_included_currency =\n                first_tax_details.raw_total_excluded_currency +\n                common_taxes_data.reduce((sum, t) => sum + t.raw_tax_amount_currency, 0);\n            first_tax_details.total_included_currency =\n                first_tax_details.total_excluded_currency +\n                first_tax_details.delta_total_excluded_currency +\n                common_taxes_data.reduce((sum, t) => sum + t.tax_amount_currency, 0);\n            first_tax_details.raw_total_included =\n                first_tax_details.raw_total_excluded +\n                common_taxes_data.reduce((sum, t) => sum + t.raw_tax_amount, 0);\n            first_tax_details.total_included =\n                first_tax_details.total_excluded +\n                first_tax_details.delta_total_excluded +\n                common_taxes_data.reduce((sum, t) => sum + t.tax_amount, 0);\n\n            const second_tax_details = {\n                raw_total_excluded_currency: tax_data_to_remove.raw_tax_amount_currency,\n                raw_total_excluded: tax_data_to_remove.raw_tax_amount,\n                total_excluded_currency: tax_data_to_remove.tax_amount_currency,\n                total_excluded: tax_data_to_remove.tax_amount,\n                delta_total_excluded_currency: 0.0,\n                delta_total_excluded: 0.0,\n                raw_total_included_currency: tax_data_to_remove.raw_tax_amount_currency,\n                raw_total_included: tax_data_to_remove.raw_tax_amount,\n                total_included_currency: tax_data_to_remove.tax_amount_currency,\n                total_included: tax_data_to_remove.tax_amount,\n                taxes_data: [],\n            };\n\n            const target_factors = [\n                {\n                    factor: first_tax_details.raw_total_excluded_currency,\n                    tax_details: first_tax_details,\n                },\n                {\n                    factor: second_tax_details.raw_total_excluded_currency,\n                    tax_details: second_tax_details,\n                },\n            ];\n            for (const remaining_tax_data of remaining_taxes_data) {\n                let first_tax_data;\n                if (tax_data_to_remove.taxes.includes(remaining_tax_data.tax)) {\n                    const new_remaining_taxes_data = this.split_tax_data(\n                        base_line,\n                        remaining_tax_data,\n                        company,\n                        target_factors\n                    );\n\n                    first_tax_data = new_remaining_taxes_data[0];\n\n                    second_tax_details.taxes_data.push(new_remaining_taxes_data[1]);\n                    second_tax_details.raw_total_included_currency +=\n                        new_remaining_taxes_data[1].raw_tax_amount_currency;\n                    second_tax_details.raw_total_included +=\n                        new_remaining_taxes_data[1].raw_tax_amount;\n                    second_tax_details.total_included_currency +=\n                        new_remaining_taxes_data[1].tax_amount_currency;\n                    second_tax_details.total_included += new_remaining_taxes_data[1].tax_amount;\n                } else {\n                    first_tax_data = remaining_tax_data;\n                }\n\n                first_tax_details.taxes_data.push(first_tax_data);\n                first_tax_details.raw_total_included_currency +=\n                    first_tax_data.raw_tax_amount_currency;\n                first_tax_details.raw_total_included += first_tax_data.raw_tax_amount;\n                first_tax_details.total_included_currency += first_tax_data.tax_amount_currency;\n                first_tax_details.total_included += first_tax_data.tax_amount;\n            }\n\n            // Split 'base_line'.\n            const first_taxes = first_tax_details.taxes_data.map((tax_data) => tax_data.tax);\n            const first_base_line = this.prepare_base_line_for_taxes_computation(base_line, {\n                tax_ids: first_taxes,\n                tax_details: first_tax_details,\n            });\n\n            const second_taxes = second_tax_details.taxes_data.map((tax_data) => tax_data.tax);\n            const second_base_line = this.prepare_base_line_for_taxes_computation(base_line, {\n                tax_ids: second_taxes,\n                price_unit:\n                    (second_tax_details.raw_total_excluded_currency +\n                        second_tax_details.taxes_data\n                            .filter((t) => t.tax.price_include)\n                            .reduce((sum, t) => sum + t.raw_tax_amount_currency, 0)) /\n                    (base_line.quantity || 1.0),\n                tax_details: second_tax_details,\n                _removed_tax_data: tax_data_to_remove,\n            });\n\n            to_process.unshift(\n                [index, first_base_line, taxes_to_exclude],\n                [index, second_base_line, taxes_to_exclude]\n            );\n        }\n\n        const final_base_lines = [];\n        new_base_lines_list.forEach((new_base_lines) => {\n            new_base_lines[0].removed_taxes_data_base_lines = new_base_lines.slice(1);\n            final_base_lines.push(new_base_lines[0]);\n        });\n        return final_base_lines;\n    },\n\n    /**\n     * [!] Mirror of the same method in account_tax.py.\n     * PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.\n     */\n    turn_removed_taxes_into_new_base_lines(\n        base_lines,\n        company,\n        { grouping_function = null, aggregate_function = null } = {}\n    ) {\n        let extra_base_lines = [];\n        for (const base_line of base_lines) {\n            extra_base_lines = extra_base_lines.concat(\n                base_line.removed_taxes_data_base_lines || []\n            );\n        }\n        return this.reduce_base_lines_with_grouping_function(extra_base_lines, {\n            grouping_function: grouping_function,\n            aggregate_function: aggregate_function,\n        });\n    },\n};\n", "import { registry } from '@web/core/registry';\nimport { Interaction } from '@web/public/interaction';\n\nexport class ExpressCheckout extends Interaction {\n    static selector = 'form[name=\"o_payment_express_checkout_form\"]';\n\n    setup() {\n        this.paymentContext = {};\n        Object.assign(this.paymentContext, this.el.dataset);\n        this.paymentContext.shippingInfoRequired = !!this.paymentContext.shippingInfoRequired;\n    }\n\n    async willStart() {\n        const expressCheckoutForm = this._getExpressCheckoutForm();\n        if (expressCheckoutForm) {\n            await this._prepareExpressCheckoutForm(expressCheckoutForm.dataset);\n        }\n    }\n\n    start() {\n        // Monitor updates of the amount on eCommerce's cart pages.\n        this.env.bus.addEventListener('cart_amount_changed', (ev) =>\n            this._updateAmount(...ev.detail)\n        );\n        // Monitor when the page is restored from the bfcache.\n        this.addListener(window, 'pageshow', this._onNavigationBack);\n    }\n\n    /**\n     * Reload the page when the page is restored from the bfcache.\n     *\n     * @param {PageTransitionEvent} event - The pageshow event.\n     * @private\n     */\n    _onNavigationBack(event) {\n        if (event.persisted) {\n            window.location.reload();\n        }\n    }\n\n    /**\n     * Return the express checkout form, if found.\n     *\n     * @private\n     * @return {Element|null} - The express checkout form.\n     */\n    _getExpressCheckoutForm() {\n        return document.querySelector(\n            'form[name=\"o_payment_express_checkout_form\"] div[name=\"o_express_checkout_container\"]'\n        );\n    }\n\n    /**\n     * Prepare the provider-specific express checkout form based on the provided data.\n     *\n     * For a provider to manage an express checkout form, it must override this method.\n     *\n     * @private\n     * @param {Object} providerData - The provider-specific data.\n     * @return {void}\n     */\n    async _prepareExpressCheckoutForm(providerData) {}\n\n    /**\n     * Prepare the params for the RPC to the transaction route.\n     *\n     * @private\n     * @param {number} providerId - The id of the provider handling the transaction.\n     * @returns {object} - The transaction route params.\n     */\n    _prepareTransactionRouteParams(providerId) {\n        return {\n            'provider_id': parseInt(providerId),\n            'payment_method_id': parseInt(this.paymentContext['paymentMethodUnknownId']),\n            'token_id': null,\n            'flow': 'direct',\n            'tokenization_requested': false,\n            'landing_route': this.paymentContext['landingRoute'],\n            'access_token': this.paymentContext['accessToken'],\n            'csrf_token': odoo.csrf_token,\n        };\n    }\n\n    /**\n     * Update the amount of the express checkout form.\n     *\n     * For a provider to manage an express form, it must override this method.\n     *\n     * @private\n     * @param {number} newAmount - The new amount.\n     * @param {number} newMinorAmount - The new minor amount.\n     * @return {void}\n     */\n    _updateAmount(newAmount, newMinorAmount) {\n        this.paymentContext.amount = parseFloat(newAmount);\n        this.paymentContext.minorAmount = parseInt(newMinorAmount);\n        this._getExpressCheckoutForm()?.classList?.toggle(\n            'd-none', this.paymentContext.amount === 0\n        );\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('payment.express_checkout', ExpressCheckout);\n", "import { registry } from '@web/core/registry';\nimport { Interaction } from '@web/public/interaction';\n\nexport class PaymentButton extends Interaction {\n    static selector = '[name=\"o_payment_submit_button\"]';\n\n    setup() {\n        this.paymentButton = this.el;\n        this.iconClass = this.paymentButton.dataset.iconClass;\n        this._enable();\n        this.env.bus.addEventListener('enablePaymentButton', this._enable.bind(this));\n        this.env.bus.addEventListener('disablePaymentButton', this._disable.bind(this));\n        this.env.bus.addEventListener('hidePaymentButton', this._hide.bind(this));\n        this.env.bus.addEventListener('showPaymentButton', this._show.bind(this));\n    }\n\n    /**\n     * Check if the payment button can be enabled and do it if so.\n     *\n     * @private\n     * @return {void}\n     */\n    _enable() {\n        if (this._canSubmit()) {\n            this._setEnabled();\n        }\n    }\n\n    /**\n     * Check whether the payment form can be submitted, i.e. whether exactly one payment option is\n     * selected.\n     *\n     * For a module to add a condition on the submission of the form, it must override this method\n     * and return whether both this method's condition and the override method's condition are met.\n     *\n     * @private\n     * @return {boolean} Whether the form can be submitted.\n     */\n    _canSubmit() {\n        const paymentForm = document.querySelector('#o_payment_form');\n        if (!paymentForm) {  // Payment form is not present.\n            return true; // Ignore the check.\n        }\n        return document.querySelectorAll('input[name=\"o_payment_radio\"]:checked').length === 1;\n    }\n\n    /**\n     * Enable the payment button.\n     *\n     * @private\n     * @return {void}\n     */\n    _setEnabled() {\n        this.paymentButton.disabled = false;\n    }\n\n    /**\n     * Disable the payment button.\n     *\n     * @private\n     * @return {void}\n     */\n    _disable() {\n        this.paymentButton.disabled = true;\n    }\n\n    /**\n     * Hide the payment button.\n     *\n     * @private\n     * @return {void}\n     */\n    _hide() {\n        this.paymentButton.classList.add('d-none');\n    }\n\n    /**\n     * Show the payment button.\n     *\n     * @private\n     * @return {void}\n     */\n    _show() {\n        this.paymentButton.classList.remove('d-none');\n    }\n}\n\nregistry.category('public.interactions').add('payment.payment_button', PaymentButton);\n", "import { browser } from '@web/core/browser/browser';\nimport { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc, RPCError } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { renderToMarkup } from '@web/core/utils/render';\nimport { Interaction } from '@web/public/interaction';\n\nexport class PaymentForm extends Interaction {\n    static selector = '#o_payment_form';\n    dynamicContent = {\n        '[name=\"o_payment_radio\"]': { 't-on-change': this.selectPaymentOption },\n        '[name=\"o_payment_delete_token\"]': { 't-on-click': this.fetchTokenData },\n        '[name=\"o_payment_expand_button\"]': { 't-on-click': this.hideExpandButton },\n        '[name=\"o_payment_submit_button\"]': { 't-on-click': this.submitForm },\n    };\n\n    // #=== INTERACTION LIFECYCLE ===#\n\n    setup() {\n        // Load the payment context from the payment form dataset.\n        this.paymentContext = {};\n        Object.assign(this.paymentContext, this.el.dataset);\n\n        this.defaultSubmitButtonLabel = document.querySelector(\n            'button[name=\"o_payment_submit_button\"]'\n        )?.textContent;\n\n        // Enable tooltips.\n        this.el.querySelectorAll('[data-bs-toggle=\"tooltip\"]').forEach(el => {\n            const tooltip = window.Tooltip.getOrCreateInstance(el);\n            this.registerCleanup(() => tooltip.dispose());\n        });\n    }\n\n    async willStart() {\n        // Expand the payment form of the selected payment option if there is only one.\n        const checkedRadio = document.querySelector('input[name=\"o_payment_radio\"]:checked');\n        if (checkedRadio) {\n            await this.waitFor(this._expandInlineForm(checkedRadio));\n            this._enableButton(false);\n        } else {\n            this._setPaymentFlow(); // Initialize the payment flow to let providers overwrite it.\n        }\n    }\n\n    // #=== EVENT HANDLERS ===#\n\n    /**\n     * Open the inline form of the selected payment option, if any.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async selectPaymentOption(ev) {\n        // Show the inputs in case they have been hidden.\n        this._showInputs();\n\n        // Disable the submit button while preparing the inline form.\n        this._disableButton();\n\n        // Unfold and prepare the inline form of the selected payment option.\n        const checkedRadio = ev.target;\n        await this.waitFor(this._expandInlineForm(checkedRadio));\n\n        // Re-enable the submit button after the inline form has been prepared.\n        this._enableButton(false);\n    }\n\n    /**\n     * Fetch data relative to the documents linked to the token and delegate them to the token\n     * deletion confirmation dialog.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async fetchTokenData(ev) {\n        ev.preventDefault();\n\n        const linkedRadio = document.getElementById(ev.currentTarget.dataset['linkedRadio']);\n        const tokenId = this._getPaymentOptionId(linkedRadio);\n        try {\n            const linkedRecordsInfo = await this.waitFor(this.services.orm.call(\n                'payment.token', 'get_linked_records_info', [tokenId]\n            ));\n            this._challengeTokenDeletion(tokenId, linkedRecordsInfo);\n        } catch (error) {\n            if (error instanceof RPCError) {\n                this._displayErrorDialog(_t(\"Cannot delete payment method\"), error.data.message);\n            } else {\n                return Promise.reject(error);\n            }\n        }\n    }\n\n    /**\n     * Hide the button to expand the payment methods section once it has been clicked.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    hideExpandButton(ev) {\n        ev.target.classList.add('d-none');\n    }\n\n    /**\n     * Update the payment context with the selected payment option and initiate its payment flow.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async submitForm(ev) {\n        ev.stopPropagation();\n        ev.preventDefault();\n\n        const checkedRadio = this.el.querySelector('input[name=\"o_payment_radio\"]:checked');\n\n        // Block the entire UI to prevent fiddling with other interactions.\n        this._disableButton(true);\n\n        // Initiate the payment flow of the selected payment option.\n        const flow = this.paymentContext.flow = this._getPaymentFlow(checkedRadio);\n        const paymentOptionId = this.paymentContext.paymentOptionId = this._getPaymentOptionId(\n            checkedRadio\n        );\n        if (flow === 'token' && this.paymentContext['assignTokenRoute']) { // Assign token flow.\n            await this._assignToken(paymentOptionId);\n        } else { // Both tokens and payment methods must process a payment operation.\n            const providerCode = this.paymentContext.providerCode = this._getProviderCode(\n                checkedRadio\n            );\n            const pmCode = this.paymentContext.paymentMethodCode = this._getPaymentMethodCode(\n                checkedRadio\n            );\n            this.paymentContext.providerId = this._getProviderId(checkedRadio);\n            if (this._getPaymentOptionType(checkedRadio) === 'token') {\n                this.paymentContext.tokenId = paymentOptionId;\n            } else { // 'payment_method'\n                this.paymentContext.paymentMethodId = paymentOptionId;\n            }\n            const inlineForm = this._getInlineForm(checkedRadio);\n            this.paymentContext.tokenizationRequested = inlineForm?.querySelector(\n                '[name=\"o_payment_tokenize_checkbox\"]'\n            )?.checked ?? this.paymentContext['mode'] === 'validation';\n            await this._initiatePaymentFlow(providerCode, paymentOptionId, pmCode, flow);\n        }\n    }\n\n    // #=== DOM MANIPULATION ===#\n\n    /**\n     * Check if the submit button can be enabled and do it if so.\n     *\n     * @private\n     * @param {boolean} unblockUI - Whether the UI should also be unblocked.\n     * @return {void}\n     */\n    _enableButton(unblockUI = true) {\n        this.env.bus.trigger('enablePaymentButton');\n        if (unblockUI) {\n            this.env.bus.trigger('ui', 'unblock');\n        }\n    }\n\n    /**\n     * Disable the submit button.\n     *\n     * @private\n     * @param {boolean} blockUI - Whether the UI should also be blocked.\n     * @return {void}\n     */\n    _disableButton(blockUI = false) {\n        this.env.bus.trigger('disablePaymentButton');\n        if (blockUI) {\n            this.env.bus.trigger('ui', 'block');\n        }\n    }\n\n    /**\n     * Show the tokenization checkbox, its label, and the submit button.\n     *\n     * @private\n     * @return {void}\n     */\n    _showInputs() {\n        // Show the tokenization checkbox and its label.\n        const tokenizeContainer = this.el.querySelector('[name=\"o_payment_tokenize_container\"]');\n        tokenizeContainer?.classList.remove('d-none');\n\n        // Show the submit button.\n        this.env.bus.trigger('showPaymentButton');\n    }\n\n    /**\n     * Hide the tokenization checkbox, its label, and the submit button.\n     *\n     * The inputs should typically be hidden when the customer has to perform additional actions in\n     * the inline form. All inputs are automatically shown again when the customer selects another\n     * payment option.\n     *\n     * @private\n     * @return {void}\n     */\n    _hideInputs() {\n        // Hide the tokenization checkbox and its label.\n        const tokenizeContainer = this.el.querySelector('[name=\"o_payment_tokenize_container\"]');\n        tokenizeContainer?.classList.add('d-none');\n\n        // Hide the submit button.\n        this.env.bus.trigger('hidePaymentButton');\n    }\n\n    /**\n     * Open the inline form of the selected payment option and collapse the others.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the payment option.\n     * @return {void}\n     */\n    async _expandInlineForm(radio) {\n        this._collapseInlineForms(); // Collapse previously opened inline forms.\n        this._setPaymentFlow(); // Reset the payment flow to let providers overwrite it.\n\n        // Prepare the inline form of the selected payment option.\n        const providerId = this._getProviderId(radio);\n        const providerCode = this._getProviderCode(radio);\n        const paymentOptionId = this._getPaymentOptionId(radio);\n        const paymentMethodCode = this._getPaymentMethodCode(radio);\n        const flow = this._getPaymentFlow(radio);\n        await this.waitFor(this._prepareInlineForm(\n            providerId, providerCode, paymentOptionId, paymentMethodCode, flow\n        ));\n\n        // Adapt the payment button's label based on the selected payment method.\n        this._adaptSubmitButtonLabel(paymentMethodCode);\n\n        // Display the prepared inline form if it contains visible elements.\n        const isVisible = element => {\n            if (\n                element.getAttribute('name') !== 'o_payment_inline_form' // Skip the container.\n                && element.classList.contains('d-none')\n            ) {\n                return false;\n            }\n            if (element.children.length === 0) {\n                return true; // The element is visible if it has no children.\n            }\n            return Array.from(element.children).some(child => isVisible(child));\n        };\n        const inlineForm = this._getInlineForm(radio);\n        if (inlineForm && isVisible(inlineForm)) {\n            inlineForm.classList.remove('d-none');\n        }\n    }\n\n    /**\n     * Collapse all inline forms of the current interaction.\n     *\n     * @private\n     * @return {void}\n     */\n    _collapseInlineForms() {\n        this.el.querySelectorAll('[name=\"o_payment_inline_form\"]').forEach(inlineForm => {\n            inlineForm.classList.add('d-none');\n        });\n    }\n\n    /**\n     * Prepare the provider-specific inline form of the selected payment option.\n     *\n     * For a provider to manage an inline form, it must override this method and render the content\n     * of the form.\n     *\n     * @private\n     * @param {number} providerId - The id of the selected payment option's provider.\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The online payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {}\n\n    /**\n     * Update the payment button's label for \"pay later\" payment methods.\n     *\n     * @private\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @return {void}\n     */\n    _adaptSubmitButtonLabel(paymentMethodCode) {\n        const buttonLabel = this._isPayLaterPaymentMethod(paymentMethodCode)\n            ? _t(\"Confirm\")\n            : this.defaultSubmitButtonLabel;\n        for (const btn of document.querySelectorAll('button[name=\"o_payment_submit_button\"]')) {\n            if (btn.textContent !== buttonLabel) {\n                btn.textContent = buttonLabel;\n            }\n        }\n    }\n\n    /**\n     * Check whether the given payment method expects immediate payment.\n     *\n     * Override this method to change the submit button label from the default label to \"Confirm\".\n     *\n     * @private\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @return {boolean}\n     */\n    _isPayLaterPaymentMethod(paymentMethodCode) {\n        return false;\n    }\n\n    /**\n     * Display an error dialog.\n     *\n     * @private\n     * @param {string} title - The title of the dialog.\n     * @param {string} errorMessage - The error message.\n     * @return {void}\n     */\n    _displayErrorDialog(title, errorMessage = '') {\n        this.services.dialog.add(ConfirmationDialog, { title: title, body: errorMessage || \"\" });\n    }\n\n    /**\n     * Display the token deletion confirmation dialog.\n     *\n     * @private\n     * @param {number} tokenId - The id of the token whose deletion was requested.\n     * @param {object} linkedRecordsInfo - The data relative to the documents linked to the token.\n     * @return {void}\n     */\n    _challengeTokenDeletion(tokenId, linkedRecordsInfo) {\n        const body = renderToMarkup('payment.deleteTokenDialog', { linkedRecordsInfo });\n        this.services.dialog.add(ConfirmationDialog, {\n            title: _t(\"Warning!\"),\n            body,\n            confirmLabel: _t(\"Confirm Deletion\"),\n            confirm: () => this._archiveToken(tokenId),\n            cancel: () => {},\n        });\n    }\n\n    // #=== PAYMENT FLOW ===#\n\n    /**\n     * Set the payment flow for the selected payment option.\n     *\n     * For a provider to manage direct payments, it must call this method and set the payment flow\n     * when its payment option is selected.\n     *\n     * @private\n     * @param {string} flow - The flow for the selected payment option. Either 'redirect', 'direct',\n     *                        or 'token'\n     * @return {void}\n     */\n    _setPaymentFlow(flow = 'redirect') {\n        if (['redirect', 'direct', 'token'].includes(flow)) {\n            this.paymentContext.flow = flow;\n        } else {\n            console.warn(`The value ${flow} is not a supported flow. Falling back to redirect.`);\n            this.paymentContext.flow = 'redirect';\n        }\n    }\n\n    /**\n     * Assign the selected token to a document through the `assignTokenRoute`.\n     *\n     * @private\n     * @param {number} tokenId - The id of the token to assign.\n     * @return {void}\n     */\n    async _assignToken(tokenId) {\n        try {\n            await this.waitFor(rpc(this.paymentContext['assignTokenRoute'], {\n                'token_id': tokenId,\n                'access_token': this.paymentContext['accessToken'],\n            }));\n            window.location = this.paymentContext['landingRoute'];\n        } catch (error) {\n            if (error instanceof RPCError) {\n                this._displayErrorDialog(_t(\"Cannot save payment method\"), error.data.message);\n                this._enableButton(); // The button has been disabled before initiating the flow.\n            } else {\n                return Promise.reject(error);\n            }\n        }\n    }\n\n    /**\n     * Make an RPC to initiate the payment flow by creating a new transaction.\n     *\n     * For a provider to do pre-processing work (e.g., perform checks on the form inputs), or to\n     * process the payment flow in its own terms (e.g., re-schedule the RPC to the transaction\n     * route), it must override this method.\n     *\n     * To alter the flow-specific processing, it is advised to override `_processRedirectFlow`,\n     * `_processDirectFlow`, or `_processTokenFlow` instead.\n     *\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _initiatePaymentFlow(providerCode, paymentOptionId, paymentMethodCode, flow) {\n        try {\n            // Create a transaction and retrieve its processing values.\n            const processingValues = await this.waitFor(rpc(\n                this.paymentContext['transactionRoute'], this._prepareTransactionRouteParams()\n            ));\n            if (processingValues.state === 'error') {\n                this._displayErrorDialog(\n                    _t(\"Payment processing failed\"), processingValues.state_message\n                );\n                this._enableButton(); // The button has been disabled before initiating the flow.\n                return;\n            }\n            if (flow === 'redirect') {\n                this._processRedirectFlow(\n                    providerCode, paymentOptionId, paymentMethodCode, processingValues\n                );\n            } else if (flow === 'direct') {\n                this._processDirectFlow(\n                    providerCode, paymentOptionId, paymentMethodCode, processingValues\n                );\n            } else if (flow === 'token') {\n                this._processTokenFlow(\n                    providerCode, paymentOptionId, paymentMethodCode, processingValues\n                );\n            }\n        } catch (error) {\n            if (error instanceof RPCError) {\n                this._displayErrorDialog(_t(\"Payment processing failed\"), error.data.message);\n                this._enableButton(); // The button has been disabled before initiating the flow.\n            }\n            return Promise.reject(error);\n        }\n    }\n\n    /**\n     * Prepare the params for the RPC to the transaction route.\n     *\n     * @private\n     * @return {object} The transaction route params.\n     */\n    _prepareTransactionRouteParams() {\n        const transactionRouteParams = {\n            'provider_id': this.paymentContext.providerId,\n            'payment_method_id': this.paymentContext.paymentMethodId ?? null,\n            'token_id': this.paymentContext.tokenId ?? null,\n            'amount': this.paymentContext['amount'] !== undefined\n                ? parseFloat(this.paymentContext['amount']) : null,\n            'flow': this.paymentContext['flow'],\n            'tokenization_requested': this.paymentContext['tokenizationRequested'],\n            'landing_route': this.paymentContext['landingRoute'],\n            'is_validation': this.paymentContext['mode'] === 'validation',\n            'access_token': this.paymentContext['accessToken'],\n            'csrf_token': odoo.csrf_token,\n        };\n        // Generic payment flows (i.e., that are not attached to a document) require extra params.\n        if (this.paymentContext['transactionRoute'] === '/payment/transaction') {\n            Object.assign(transactionRouteParams, {\n                'currency_id': this.paymentContext['currencyId']\n                    ? parseInt(this.paymentContext['currencyId']) : null,\n                'partner_id': parseInt(this.paymentContext['partnerId']),\n                'reference_prefix': this.paymentContext['referencePrefix']?.toString(),\n            });\n        }\n        return transactionRouteParams;\n    }\n\n    /**\n     * Redirect the customer by submitting the redirect form included in the processing values.\n     *\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n    _processRedirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {\n        // Create and configure the form element with the content rendered by the server.\n        const div = document.createElement('div');\n        div.innerHTML = processingValues['redirect_form_html'];\n        const redirectForm = div.querySelector('form');\n        redirectForm.setAttribute('id', 'o_payment_redirect_form');\n        redirectForm.setAttribute('target', '_top');  // Ensures redirections when in an iframe.\n\n        // Submit the form.\n        document.body.appendChild(redirectForm);\n        redirectForm.submit();\n    }\n\n   /**\n     * Process the provider-specific implementation of the direct payment flow.\n     *\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n   _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {}\n\n    /**\n     * Redirect the customer to the status route.\n     *\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n    _processTokenFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {\n        // The flow is already completed as payments by tokens are immediately processed.\n        window.location = '/payment/status';\n    }\n\n    /**\n     * Archive the provided token.\n     *\n     * @private\n     * @param {number} tokenId - The id of the token whose deletion was requested.\n     * @return {void}\n     */\n    async _archiveToken(tokenId) {\n        try {\n            await this.waitFor(rpc('/payment/archive_token', {\n                'token_id': tokenId,\n            }));\n            browser.location.reload();\n        } catch (error) {\n            if (error instanceof RPCError) {\n                this._displayErrorDialog(\n                    _t(\"Cannot delete payment method\"), error.data.message\n                );\n            } else {\n                return Promise.reject(error);\n            }\n        }\n    }\n\n    // #=== GETTERS ===#\n\n    /**\n     * Determine and return the inline form of the selected payment option.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the payment option.\n     * @return {Element | null} The inline form of the selected payment option, if any.\n     */\n    _getInlineForm(radio) {\n        const inlineFormContainer = radio.closest('[name=\"o_payment_option\"]');\n        return inlineFormContainer?.querySelector('[name=\"o_payment_inline_form\"]');\n    }\n\n    /**\n     * Determine and return the payment flow of the selected payment option.\n     *\n     * As some providers implement both direct payments and the payment with redirection flow, we\n     * cannot infer it from the radio button only. The radio button indicates only whether the\n     * payment option is a token. If not, the payment context is looked up to determine whether the\n     * flow is 'direct' or 'redirect'.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the payment option.\n     * @return {string} The flow of the selected payment option: 'redirect', 'direct' or 'token'.\n     */\n    _getPaymentFlow(radio) {\n        // The flow is read from the payment context too in case it was forced in a custom implem.\n        if (this._getPaymentOptionType(radio) === 'token' || this.paymentContext.flow === 'token') {\n            return 'token';\n        } else if (this.paymentContext.flow === 'redirect') {\n            return 'redirect';\n        } else {\n            return 'direct';\n        }\n    }\n\n    /**\n     * Determine and return the code of the selected payment method.\n     *\n     * @private\n     * @param {HTMLElement} radio - The radio button linked to the payment method.\n     * @return {string} The code of the selected payment method.\n     */\n    _getPaymentMethodCode(radio) {\n        return radio.dataset['paymentMethodCode'];\n    }\n\n    /**\n     * Determine and return the id of the selected payment option.\n     *\n     * @private\n     * @param {HTMLElement} radio - The radio button linked to the payment option.\n     * @return {number} The id of the selected payment option.\n     */\n    _getPaymentOptionId(radio) {\n        return Number(radio.dataset['paymentOptionId']);\n    }\n\n    /**\n     * Determine and return the type of the selected payment option.\n     *\n     * @private\n     * @param {HTMLElement} radio - The radio button linked to the payment option.\n     * @return {string} The type of the selected payment option: 'token' or 'payment_method'.\n     */\n    _getPaymentOptionType(radio) {\n        return radio.dataset['paymentOptionType'];\n    }\n\n    /**\n     * Determine and return the id of the provider of the selected payment option.\n     *\n     * @private\n     * @param {HTMLElement} radio - The radio button linked to the payment option.\n     * @return {number} The id of the provider of the selected payment option.\n     */\n    _getProviderId(radio) {\n        return Number(radio.dataset['providerId']);\n    }\n\n    /**\n     * Determine and return the code of the provider of the selected payment option.\n     *\n     * @private\n     * @param {HTMLElement} radio - The radio button linked to the payment option.\n     * @return {string} The code of the provider of the selected payment option.\n     */\n    _getProviderCode(radio) {\n        return radio.dataset['providerCode'];\n    }\n\n    /**\n     * Determine and return the state of the provider of the selected payment option.\n     *\n     * @private\n     * @param {HTMLElement} radio - The radio button linked to the payment option.\n     * @return {string} The state of the provider of the selected payment option.\n     */\n    _getProviderState(radio) {\n        return radio.dataset['providerState'];\n    }\n\n}\n\nregistry.category('public.interactions').add('payment.payment_form', PaymentForm);\n", "import { ConnectionLostError, rpc, RPCError } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { Interaction } from '@web/public/interaction';\n\nexport class PaymentPostProcessing extends Interaction {\n    static selector = 'div[name=\"o_payment_status\"]';\n\n    setup() {\n        this.timeout = 0;\n        this.pollCount = 0;\n    }\n\n    start() {\n        this.poll();\n    }\n\n    poll() {\n        this.updateTimeout();\n        this.waitForTimeout(async () => {\n            try {\n                // Fetch the post-processing values from the server.\n                const postProcessingValues = await this.waitFor(\n                    rpc('/payment/status/poll', { csrf_token: odoo.csrf_token })\n                );\n\n                // Redirect the user to the landing route if the transaction reached a final state.\n                const { provider_code, state, landing_route } = postProcessingValues;\n                if (PaymentPostProcessing.getFinalStates(provider_code).has(state)) {\n                    window.location = landing_route;\n                } else {\n                    this.poll();\n                }\n            } catch (error) {\n                const isRetryError = error instanceof RPCError && error.data.message === 'retry';\n                const isConnectionLostError = error instanceof ConnectionLostError;\n                if (isRetryError || isConnectionLostError) {\n                    this.poll();\n                }\n                if (!isRetryError) {\n                    throw error;\n                }\n            }\n        }, this.timeout);\n    }\n\n    static getFinalStates(providerCode) {\n        return new Set(['authorized', 'done', 'cancel', 'error']);\n    }\n\n    updateTimeout() {\n        if (this.pollCount >= 1 && this.pollCount < 10) {\n            this.timeout = 3000;\n        }\n        if (this.pollCount >= 10 && this.pollCount < 20) {\n            this.timeout = 10000;\n        } else if (this.pollCount >= 20) {\n            this.timeout = 30000;\n        }\n        this.pollCount++;\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('payment.payment_post_processing', PaymentPostProcessing);\n", "import { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n    /**\n     * Set whether we are paying an installment before submitting.\n     *\n     * @override method from payment.payment_form\n     * @param {Event} ev\n     * @returns {void}\n     */\n    async submitForm(ev) {\n        ev.stopPropagation();\n        ev.preventDefault();\n\n        const paymentDialog = this.el.closest(\"#pay_with\");\n        const chosenPaymentDetails = paymentDialog\n            ? paymentDialog.querySelector(\".o_btn_payment_tab.active\")\n            : null;\n        if (chosenPaymentDetails){\n            if (chosenPaymentDetails.id === \"o_payment_installments_tab\") {\n                this.paymentContext.amount = parseFloat(this.paymentContext.invoiceNextAmountToPay);\n            } else {\n                this.paymentContext.amount = parseFloat(this.paymentContext.invoiceAmountDue);\n            }\n        }\n        await super.submitForm(...arguments);\n    },\n\n        /**\n     * Prepare the params for the RPC to the transaction route.\n     *\n     * @override method from payment.payment_form\n     * @private\n     * @return {object} The transaction route params.\n     */\n        _prepareTransactionRouteParams() {\n            const transactionRouteParams = super._prepareTransactionRouteParams(...arguments);\n            transactionRouteParams.payment_reference = this.paymentContext.paymentReference;\n            return transactionRouteParams;\n        },\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class PortalInvoicePagePayment extends Interaction {\n    static selector = \"#portal_pay\";\n\n    setup() {\n        if (this.el.dataset.payment) {\n            (new Modal(\"#pay_with\")).show();\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"account_payment.portal_invoice_page_payment\", PortalInvoicePagePayment);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { deserializeDateTime } from \"@web/core/l10n/dates\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nconst { DateTime } = luxon;\n\nexport class PortalMyInvoicesPaymentList extends Interaction {\n    static selector = \".o_portal_my_doc_table\";\n\n    start() {\n        const today = DateTime.now().startOf(\"day\");\n        const dueDateEls = this.el.querySelectorAll(\".o_portal_invoice_due_date\");\n        for (const dueDateEl of dueDateEls) {\n            const dateTime = deserializeDateTime(dueDateEl.getAttribute(\"datetime\")).startOf(\"day\");\n            const diff = dateTime.diff(today).as(\"days\");\n\n            const dueDateLabel =\n                (diff === 0) ? _t(\"due today\") :\n                    (diff > 0)\n                        ? _t(\"due in %s day(s)\", Math.abs(diff).toFixed())\n                        : _t(\"%s day(s) overdue\", Math.abs(diff).toFixed());\n\n            // We use `.createTextNode()` to escape possible HTML in translations (XSS)\n            dueDateEl.replaceChildren(document.createTextNode(dueDateLabel));\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"account_payment.portal_my_invoices_payment_list\", PortalMyInvoicesPaymentList);\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\n\nexport class PortalPrepayment extends Interaction {\n    static selector = \".o_portal_sale_sidebar\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _amountPrepaymentButton: () => this.amountPrepaymentButton,\n        _amountTotalButton: () => this.amountTotalButton,\n    };\n    dynamicContent = {\n        _amountPrepaymentButton: {\n            't-on-click': () => this.reloadAmount(true),\n            't-att-class': () => ({ 'active': this.isDownPayment }),\n        },\n        _amountTotalButton: {\n            't-on-click': () => this.reloadAmount(false),\n            't-att-class': () => ({ 'active': !this.isDownPayment }),\n        },\n        'span[id=\"o_sale_portal_use_amount_prepayment\"]': {\n            't-att-class': () => ({ 'd-none': !this.isDownPayment }),\n        },\n        'span[id=\"o_sale_portal_use_amount_total\"]': {\n            't-att-class': () => ({ 'd-none': this.isDownPayment }),\n        },\n    };\n\n    setup() {\n        this.amountPrepaymentButton = document.querySelector(\n            'button[name=\"o_sale_portal_amount_prepayment_button\"]'\n        );\n        this.amountTotalButton = document.querySelector(\n            'button[name=\"o_sale_portal_amount_total_button\"]'\n        );\n        const params = new URLSearchParams(window.location.search);\n        if (params.has('amount_selection')) {\n           this.isDownPayment = params.get('amount_selection') === 'down_payment'\n        } else if (params.has('payment_amount')) {\n            const paymentAmount = params.get('payment_amount');\n            this.isDownPayment = Number(paymentAmount) < Number(this.el.dataset.orderAmountTotal);\n        } else {\n            this.isDownPayment = true;\n        }\n        this.showPaymentModal = params.has('payment_amount') || params.has('amount_selection');\n    }\n\n    start() {\n        // When updating the amount re-open the modal.\n        if (this.showPaymentModal) {\n            document.querySelector(\"#o_sale_portal_paynow\")?.click();\n        }\n    }\n\n    reloadAmount(isDownPayment) {\n        const searchParams = new URLSearchParams(window.location.search);\n        searchParams.set('amount_selection', isDownPayment ? 'down_payment' : 'full_amount');\n        window.location.search = searchParams.toString();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"sale.portal_prepayment\", PortalPrepayment);\n", "import { patch } from \"@web/core/utils/patch\";\nimport { PortalHomeCounters } from '@portal/interactions/portal_home_counters';\n\npatch(PortalHomeCounters.prototype, {\n    /**\n     * @override\n     */\n    getCountersAlwaysDisplayed() {\n        return super.getCountersAlwaysDisplayed(...arguments).concat(['order_count']);\n    },\n});\n", "import { Sidebar } from \"@portal/interactions/sidebar\";\nimport { registry } from \"@web/core/registry\";\n\nexport class SaleSidebar extends Sidebar {\n    static selector = \".o_portal_sale_sidebar\";\n\n    setup() {\n        super.setup();\n        this.spyWatched = document.querySelector(\"body[data-target='.navspy']\");\n    }\n\n    start() {\n        super.start();\n        // Nav Menu ScrollSpy\n        this.generateMenu();\n        // After signature, automatically open the popup for payment\n        const searchParams = new URLSearchParams(window.location.search.substring(1));\n        if (searchParams.get(\"allow_payment\") === \"yes\") {\n            this.el.querySelector(\"#o_sale_portal_paynow\")?.click();\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"sale.sale_sidebar\", SaleSidebar);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { rpc } from \"@web/core/network/rpc\";\n\nexport class SaleUpdateLineButton extends Interaction {\n    static selector = \".o_portal_sale_sidebar\";\n    dynamicContent = {\n        \"a.js_update_line_json\": {\n            \"t-on-click.prevent.withTarget\": this.onUpdateLineClick,\n        },\n        \".js_quantity\": {\n            \"t-on-change.prevent.withTarget\": this.onQuantityChange,\n        },\n    };\n\n    setup() {\n        this.orderDetail = this.el.querySelector(\"table#sales_order_table\").dataset;\n    }\n\n    /**\n     * @param {number} orderId\n     * @param {Object} params\n     */\n    callUpdateLineRoute(orderId, params) {\n        return rpc(\"/my/orders/\" + orderId + \"/update_line_dict\", params);\n    }\n\n    refreshOrderUI() {\n        window.location.reload();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onQuantityChange(ev, currentTargetEl) {\n        const quantity = parseInt(currentTargetEl.value);\n        await this.waitFor(this.callUpdateLineRoute(this.orderDetail.orderId, {\n            \"access_token\": this.orderDetail.token,\n            \"input_quantity\": quantity >= 0 ? quantity : false,\n            \"line_id\": currentTargetEl.dataset.lineId,\n        }));\n        this.refreshOrderUI();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onUpdateLineClick(ev, currentTargetEl) {\n        await this.waitFor(this.callUpdateLineRoute(this.orderDetail.orderId, {\n            \"access_token\": this.orderDetail.token,\n            \"line_id\": currentTargetEl.dataset.lineId,\n            \"remove\": currentTargetEl.dataset.remove,\n        }));\n        this.refreshOrderUI();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"sale_management.sale_update_line_button\", SaleUpdateLineButton);\n", "import { session } from \"@web/session\";\nimport { loadJS } from \"@web/core/assets\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport class ReCaptcha {\n    /**\n     * @override\n     */\n    constructor() {\n        this._publicKey = session.recaptcha_public_key;\n    }\n    /**\n     * Loads the recaptcha libraries.\n     *\n     * @returns {Promise|boolean} promise if libs are loading else false if the\n     * reCaptcha is disabled or its key is empty.\n     */\n    loadLibs() {\n        if (this._publicKey) {\n            this._recaptchaReady = loadJS(`https://www.recaptcha.net/recaptcha/api.js?render=${encodeURIComponent(this._publicKey)}`)\n                .then(() => new Promise(resolve => window.grecaptcha.ready(() => resolve())));\n            return this._recaptchaReady.then(() => !!document.querySelector('.grecaptcha-badge'));\n        }\n        return false;\n    }\n    /**\n     * Returns an object with the token if reCaptcha call succeeds\n     * If no key is set an object with a message is returned\n     * If an error occurred an object with the error message is returned\n     *\n     * @param {string} action\n     * @returns {Promise|Object}\n     */\n    async getToken(action) {\n        if (!this._publicKey) {\n            return {\n                message: _t(\"reCAPTCHA disabled or no site key has been configured. Please check your settings.\"),\n            };\n        }\n        await this._recaptchaReady;\n        try {\n            return {\n                token: await window.grecaptcha.execute(this._publicKey, {action: action})\n            };\n        } catch {\n            return {\n                error: _t(\"The recaptcha site key is invalid.\"),\n            };\n        }\n    }\n}\n\nexport default {\n    ReCaptcha: ReCaptcha,\n};\n", "/** @odoo-module **/\n\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { ReCaptcha } from \"@google_recaptcha/js/recaptcha\";\nimport { addLoadingEffect } from \"@web/core/utils/ui\";\n\nexport class RecaptchaForm extends Interaction {\n    static selector = \"form[data-captcha]\";\n    dynamicContent = {\n        _root: { \"t-on-submit\": this.onSubmit },\n    };\n\n    setup() {\n        this.recaptcha = new ReCaptcha();\n    }\n\n    async willStart() {\n        await this.recaptcha.loadLibs();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    async onSubmit(ev) {\n        const submitEl = this.el.querySelector(\"button[type='submit']\");\n        if (!submitEl.disabled) {\n            addLoadingEffect(submitEl);\n        }\n        if (!this.el.querySelector(\"input[name='recaptcha_token_response']\")) {\n            ev.preventDefault();\n            if (!submitEl.disabled) {\n                addLoadingEffect(submitEl);\n            }\n            const action = this.el.dataset.captcha || \"generic\";\n            const tokenCaptcha = await this.waitFor(this.recaptcha.getToken(action));\n            if (tokenCaptcha.token) {\n                // Do not send an 'undefined' value when reCAPTCHA is disabled\n                // or not configured.\n                const inputEl = document.createElement(\"input\");\n                inputEl.name = \"recaptcha_token_response\";\n                inputEl.type = \"hidden\";\n                inputEl.value = tokenCaptcha.token;\n                this.insert(inputEl);\n            }\n            this.el.submit();\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"google_recaptcha.recaptcha_form\", RecaptchaForm);\n", "// Scrolling util functions needed by the frontend apps and sub-modules. These\n// functions indeed take into account all frontend-specific concepts (like the\n// header at the top of the page, the wrapwrap,...) which are not considered in\n// the `@web/core/utils/scrolling` utils.\n\nimport { getScrollingElement } from \"@web/core/utils/scrolling\";\n\n/**\n * Determines if an element is scrollable.\n *\n * @param {Element} element - the element to check\n * @returns {Boolean}\n */\nfunction isScrollable(element) {\n    if (!element) {\n        return false;\n    }\n    const overflowY = window.getComputedStyle(element).overflowY;\n    return (\n        overflowY === \"auto\" ||\n        overflowY === \"scroll\" ||\n        (overflowY === \"visible\" && element === element.ownerDocument.scrollingElement)\n    );\n}\n\n/**\n * Finds the closest scrollable element for the given element.\n *\n * @param {Element} element - The element to find the closest scrollable element for.\n * @returns {Element} The closest scrollable element.\n */\nexport function closestScrollable(element) {\n    const document = element.ownerDocument || window.document;\n\n    while (element && element !== document.scrollingElement) {\n        if (element instanceof Document) {\n            return null;\n        }\n        if (isScrollable(element)) {\n            return element;\n        }\n        element = element.parentElement;\n    }\n    return element || document.scrollingElement;\n}\n\n/**\n * Computes the size by which a scrolling point should be decreased so that\n * the top fixed elements of the page appear above that scrolling point.\n *\n * @param {Document} [doc=document]\n * @returns {number}\n */\nfunction scrollFixedOffset(doc = document) {\n    let size = 0;\n    const elements = doc.querySelectorAll(\".o_top_fixed_element\");\n\n    elements.forEach((el) => {\n        size += el.offsetHeight;\n    });\n\n    return size;\n}\n\n/**\n * @param {HTMLElement|string} el - the element to scroll to. If \"el\" is a\n *      string, it must be a valid selector of an element in the DOM or\n *      '#top' or '#bottom'. If it is an HTML element, it must be present\n *      in the DOM.\n *      Limitation: if the element is using a fixed position, this\n *      function cannot work except if is the header (el is then either a\n *      string set to '#top' or an HTML element with the \"top\" id) or the\n *      footer (el is then a string set to '#bottom' or an HTML element\n *      with the \"bottom\" id) for which exceptions have been made.\n * @param {number} [options] - options for the scroll behavior\n * @param {number} [options.extraOffset=0]\n *      extra offset to add on top of the automatic one (the automatic one\n *      being computed based on fixed header sizes)\n * @param {number} [options.forcedOffset]\n *      offset used instead of the automatic one (extraOffset will be\n *      ignored too)\n * @param {HTMLElement} [options.scrollable] the element to scroll\n * @param {number} [options.duration] the scroll duration in ms\n * @return {Promise}\n */\nexport function scrollTo(el, options = {}) {\n    if (!el) {\n        throw new Error(\"The scrollTo function was called without any given element\");\n    }\n    if (typeof el === \"string\") {\n        el = document.querySelector(el);\n    }\n    const isTopOrBottomHidden = el === \"top\" || el === \"bottom\";\n    const scrollable = isTopOrBottomHidden\n        ? document.scrollingElement\n        : options.scrollable || closestScrollable(el.parentElement);\n    const scrollDocument = scrollable.ownerDocument;\n    const isInOneDocument = isTopOrBottomHidden || scrollDocument === el.ownerDocument;\n    const iframe =\n        !isInOneDocument &&\n        Array.from(scrollable.querySelectorAll(\"iframe\")).find((node) =>\n            node.contentDocument.contains(el)\n        );\n    const topLevelScrollable = getScrollingElement(scrollDocument);\n\n    function _computeScrollTop() {\n        if (el === \"#top\" || el.id === \"top\") {\n            return 0;\n        }\n        if (el === \"#bottom\" || el.id === \"bottom\") {\n            return scrollable.scrollHeight - scrollable.clientHeight;\n        }\n\n        el.classList.add(\"o_check_scroll_position\");\n        let offsetTop = el.getBoundingClientRect().top + window.scrollY;\n        el.classList.remove(\"o_check_scroll_position\");\n        if (el.classList.contains(\"d-none\")) {\n            el.classList.remove(\"d-none\");\n            offsetTop = el.getBoundingClientRect().top + window.scrollY;\n            el.classList.add(\"d-none\");\n        }\n        const isDocScrollingEl = scrollable === el.ownerDocument.scrollingElement;\n        let elPosition =\n            offsetTop -\n            (scrollable.getBoundingClientRect().top +\n                window.scrollY -\n                (isDocScrollingEl ? 0 : scrollable.scrollTop));\n        if (!isInOneDocument && iframe) {\n            elPosition += iframe.getBoundingClientRect().top + window.scrollY;\n        }\n        let offset = options.forcedOffset;\n        if (offset === undefined) {\n            offset =\n                (scrollable === topLevelScrollable ? scrollFixedOffset(scrollDocument) : 0) +\n                (options.extraOffset || 0);\n        }\n        return Math.max(0, elPosition - offset);\n    }\n\n    return new Promise((resolve) => {\n        const start = scrollable.scrollTop;\n        const duration = options.duration || 600;\n        const startTime = performance.now();\n\n        function animateScroll(currentTime) {\n            const elapsedTime = currentTime - startTime;\n            const progress = Math.min(elapsedTime / duration, 1);\n            const easeInOutQuad =\n                progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;\n            // Recompute the scroll destination every time, to adapt to any\n            // occurring change that would modify the scroll offset.\n            const change = _computeScrollTop() - start;\n            const newScrollTop = start + change * easeInOutQuad;\n\n            scrollable.scrollTop = newScrollTop;\n\n            if (elapsedTime < duration) {\n                requestAnimationFrame(animateScroll);\n            } else {\n                resolve();\n            }\n        }\n\n        requestAnimationFrame(animateScroll);\n    });\n}\n", "// import { registry } from \"@web/core/registry\";\nimport { Component, xml, useState } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\n\n// -----------------------------------------------------------------------------\n// Example of mounted component\n// -----------------------------------------------------------------------------\nexport class Counter extends Component {\n    static selector = \"#wrapwrap h1\";\n    static template = xml`\n        <div class=\"btn btn-primary\" t-on-click=\"increment\">\n            Counter. Value=<t t-esc=\"state.value\"/>\n        </div>`;\n    static props = {};\n\n    setup() {\n        this.state = useState({ value: 1 });\n        this.notification = useService(\"notification\");\n    }\n\n    increment(ev) {\n        ev.stopPropagation();\n        this.state.value++;\n        this.notification.add(`Example of a service: ${this.state.value}`);\n    }\n}\n\n/*\nregistry.category(\"public.interactions\").add(\"website.counter\", Counter);\n*/\n", "import { scrollTo } from \"@html_builder/utils/scrolling\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class AnchorSlide extends Interaction {\n    static selector = \"a[href^='/'][href*='#'], a[href^='#']\";\n    dynamicContent = {\n        _root: {\n            \"t-on-click\": this.animateClick,\n        },\n    };\n\n    /**\n     * @param {HTMLElement} el the element to scroll to.\n     * @param {string} [scrollValue='true'] scroll value\n     * @returns {Promise}\n     */\n    scrollTo(el, scrollValue = \"true\") {\n        return scrollTo(el, {\n            duration: scrollValue === \"true\" ? 500 : 0,\n            extraOffset: this.computeExtraOffset(),\n        });\n    }\n\n    computeExtraOffset() {\n        return 0;\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    animateClick(ev) {\n        const ensureSlash = (path) => (path.endsWith(\"/\") ? path : path + \"/\");\n        if (ensureSlash(this.el.pathname) !== ensureSlash(window.location.pathname)) {\n            return;\n        }\n        // Avoid flicker at destination in case of ending \"/\" difference.\n        if (this.el.pathname !== window.location.pathname) {\n            this.el.pathname = window.location.pathname;\n        }\n        let hash = this.el.hash;\n        if (!hash.length) {\n            return;\n        }\n        // Escape special characters to make the selector work.\n        hash = \"#\" + CSS.escape(hash.substring(1));\n        const anchorEl = this.el.ownerDocument.querySelector(hash);\n        const scrollValue = anchorEl?.dataset.anchor;\n        // No need to scroll when target is _blank as it should open in new tab\n        if (!anchorEl || !scrollValue || this.el.target === \"_blank\") {\n            return;\n        }\n\n        const offcanvasEl = this.el.closest(\".offcanvas.o_navbar_mobile\");\n        if (offcanvasEl && offcanvasEl.classList.contains(\"show\")) {\n            // Special case for anchors in offcanvas in mobile: we can't just\n            // scrollTo() after preventDefault because preventDefault would\n            // prevent the offcanvas to be closed. The choice is then to close\n            // it ourselves manually and once it's fully closed, then start our\n            // own smooth scrolling.\n            ev.preventDefault();\n            Offcanvas.getInstance(offcanvasEl).hide();\n            this.addListener(\n                offcanvasEl,\n                \"hidden.bs.offcanvas\",\n                () => this.manageScroll(hash, anchorEl, scrollValue),\n                // the listener must be automatically removed when invoked\n                { once: true }\n            );\n        } else {\n            ev.preventDefault();\n            this.manageScroll(hash, anchorEl, scrollValue);\n        }\n    }\n\n    /**\n     * @param {string} hash\n     * @param {HTMLElement} anchorEl the element to scroll to.\n     * @param {string} [scrollValue='true'] scroll value\n     */\n    manageScroll(hash, anchorEl, scrollValue = \"true\") {\n        if (hash === \"#top\" || hash === \"#bottom\") {\n            // If the anchor targets #top or #bottom, directly call the\n            // \"scrollTo\" function. The reason is that the header or the footer\n            // could have been removed from the DOM. By receiving a string as\n            // parameter, the \"scrollTo\" function handles the scroll to the top\n            // or to the bottom of the document even if the header or the\n            // footer is removed from the DOM.\n            this.scrollTo(hash);\n        } else {\n            this.scrollTo(anchorEl, scrollValue);\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.anchor_slide\", AnchorSlide);\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { getScrollingElement } from \"@web/core/utils/scrolling\";\n\nexport class AnimateOverflow extends Interaction {\n    static selector = \"#wrapwrap\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _scrollingElement: () => this.scrollingElement,\n    };\n    dynamicContent = {\n        _scrollingElement: {\n            \"t-att-class\": () => ({\n                o_wanim_overflow_xy_hidden:\n                    this.forceOverflowXYHidden || this.hasAnimationInProgress,\n            }),\n        },\n        _root: {\n            \"t-on-updatecontent.noUpdate\": (ev) => {\n                if (ev.target.classList.contains(\"o_animate\")) {\n                    this.updateContent();\n                }\n            },\n        },\n    };\n\n    setup() {\n        this.scrollingElement = getScrollingElement(this.el.ownerDocument);\n        const animatedElements = this.el.querySelectorAll(\".o_animate\");\n        // Fix for \"transform: none\" not overriding keyframe transforms on\n        // some iPhone using Safari. Note that all animated elements are checked\n        // (not only one) as the bug is not systematic and may depend on some\n        // other conditions (for example: an animated image in a block which is\n        // hidden on mobile would not have the issue).\n        this.forceOverflowXYHidden = [...animatedElements].some(\n            (el) => window.getComputedStyle(el).transform !== \"none\"\n        );\n    }\n\n    get hasAnimationInProgress() {\n        return this.el.querySelector(\".o_animating\") != null;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.animate_overflow\", AnimateOverflow);\n\nregistry.category(\"public.interactions.edit\").add(\"website.animate_overflow\", {\n    Interaction: AnimateOverflow,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { getScrollingElement, isScrollableY } from \"@web/core/utils/scrolling\";\nimport { isVisible } from \"@web/core/utils/ui\";\n\nexport class Animation extends Interaction {\n    static selector = \".o_animate\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _scrollingTarget: () => this.scrollingTarget,\n        _windowUnlessDropdown: () => this.windowUnlessDropdown,\n    };\n    dynamicContent = {\n        _window: { \"t-on-resize\": this.scrollWebsiteAnimate },\n        _windowUnlessDropdown: {\n            \"t-on-shown.bs.modal\": this.scrollWebsiteAnimate,\n            \"t-on-slid.bs.carousel\": this.scrollWebsiteAnimate,\n            \"t-on-shown.bs.tab\": this.scrollWebsiteAnimate,\n            \"t-on-shown.bs.collapse\": this.scrollWebsiteAnimate,\n        },\n        _scrollingTarget: {\n            // Setting capture to true allows to take advantage of event\n            // bubbling for events that otherwise don\u2019t support it. (e.g. useful\n            // when scrolling a modal)\n            \"t-on-scroll.capture\": this.throttled(this.scrollWebsiteAnimate),\n        },\n        _root: {\n            \"t-att-class\": (el) => ({\n                o_animating: this.isAnimating,\n                o_animated: this.isAnimated,\n                o_animate_in_dropdown: !!el.closest(\".dropdown\"),\n                o_animate_preview: undefined,\n            }),\n            \"t-att-style\": (el) => {\n                const result = {\n                    \"animation-name\": this.isResetting ? \"dummy-none\" : undefined,\n                    \"animation-play-state\":\n                        this.isResetting || this.isAnimateOnScroll ? undefined : this.playState,\n                    // The ones which are invisible in state 0 (like fade_in for\n                    // example) will stay invisible.\n                    visibility: \"visible\",\n                };\n                // Avoid resetting animation-delay upon stop when it is not\n                // supposed to be modified at all.\n                if (this.isAnimateOnScroll) {\n                    result[\"animation-delay\"] = this.delay;\n                }\n                return result;\n            },\n        },\n    };\n\n    offsetRatio = 0.3; // Dynamic offset ratio: 0.3 = (element's height/3)\n    offsetMin = 10; // Minimum offset for small elements (in pixels)\n\n    setup() {\n        this.wrapwrapEl = document.querySelector(\"#wrapwrap\");\n        this.windowUnlessDropdown = this.el.closest(\".dropdown\") ? [] : window;\n        this.scrollingElement = this.findScrollingElement();\n        this.scrollingTarget = isScrollableY(this.scrollingElement)\n            ? this.scrollingElement\n            : this.scrollingElement.ownerDocument.defaultView;\n        this.isAnimating = false;\n        this.isAnimated = false;\n        this.isAnimateOnScroll = this.el.classList.contains(\"o_animate_on_scroll\");\n        this.isResetting = false;\n        const style = window.getComputedStyle(this.el);\n        this.playState = style.animationPlayState;\n        this.delay = undefined;\n    }\n\n    start() {\n        if (this.el.closest(\".dropdown\")) {\n            return;\n        }\n        // By default, elements are hidden by the css of o_animate.\n        // Render elements and trigger the animation then pause it in state 0.\n        if (!this.isAnimateOnScroll) {\n            this.resetAnimation();\n            this.updateContent();\n        }\n        this.scrollWebsiteAnimate();\n        this.updateContent();\n    }\n\n    findScrollingElement() {\n        return getScrollingElement(this.el.ownerDocument);\n    }\n\n    /**\n     * Starts animation and/or update element's state.\n     */\n    startAnimation() {\n        // Forces the browser to redraw using setTimeout.\n        this.waitForTimeout(() => {\n            this.isAnimating = true;\n            this.playState = \"running\";\n            for (const eventName of [\n                \"webkitAnimationEnd\",\n                \"oanimationend\",\n                \"msAnimationEnd\",\n                \"animationend\",\n            ]) {\n                this.addListener(\n                    this.el,\n                    eventName,\n                    () => {\n                        this.isAnimating = false;\n                        this.isAnimated = true;\n                        window.dispatchEvent(new Event(\"resize\"));\n                    },\n                    { once: true }\n                );\n            }\n        });\n    }\n\n    resetAnimation() {\n        this.isResetting = true;\n        this.isAnimated = false;\n        this.isAnimating = false;\n        this.updateContent();\n        // trigger a DOM reflow\n        void this.el.offsetWidth;\n        this.isResetting = false;\n        this.playState = \"paused\";\n    }\n\n    /**\n     * Gets element top offset by not taking CSS transforms into calculations.\n     *\n     * @param {HTMLElement} el\n     * @param {HTMLElement} [topEl] if specified, calculates the top distance to\n     *     this element.\n     */\n    getElementOffsetTop(el, topEl) {\n        // Loop through the DOM tree and add its parent's offset to get page offset.\n        let top = 0;\n        do {\n            top += el.offsetTop || 0;\n            el = el.offsetParent;\n            if (topEl && el === topEl) {\n                return top;\n            }\n        } while (el);\n        return top;\n    }\n\n    scrollWebsiteAnimate() {\n        const el = this.el;\n        if (el.classList.contains(\"o_animate_in_dropdown\")) {\n            return;\n        }\n        const windowsHeight = window.innerHeight;\n        const elHeight = el.offsetHeight;\n        const elOffset = this.isAnimateOnScroll\n            ? 0\n            : Math.max(elHeight * this.offsetRatio, this.offsetMin);\n\n        // We need to offset for the change in position from some animation.\n        // So we get the top value by not taking CSS transforms into calculations.\n        // Cookies bar might be opened and considered as a modal but it is\n        // not really one when there is no backdrop (eg 'discrete' layout),\n        // and should not be used as scrollTop value.\n        const closestModal = el.closest(\".modal\");\n        let scrollTop = this.scrollingElement.scrollTop;\n        if (closestModal && isVisible(closestModal)) {\n            scrollTop = closestModal.classList.contains(\"s_popup_no_backdrop\")\n                ? closestModal.querySelector(\".modal-content\").scrollTop\n                : closestModal.scrollTop;\n        }\n        const elTop = this.getElementOffsetTop(el) - scrollTop;\n        let visible;\n        const footerEl = el.closest(\".o_footer_slideout\");\n        if (footerEl && this.wrapwrapEl.classList.contains(\"o_footer_effect_enable\")) {\n            // Since the footer slideout is always in the viewport but not\n            // always displayed, the way to calculate if an element is\n            // visible in the footer is different. We decided to handle this\n            // case specifically instead of a generic solution using\n            // elementFromPoint as it is a rare case and the implementation\n            // would have been too complicated for such a small use case.\n            const actualScroll = scrollTop + windowsHeight;\n            const totalScrollHeight = this.wrapwrapEl.scrollHeight;\n            const heightFromFooter = this.getElementOffsetTop(el, footerEl);\n            visible = actualScroll >= totalScrollHeight - heightFromFooter - elHeight + elOffset;\n        } else {\n            visible = windowsHeight > elTop + elOffset && 0 < elTop + elHeight - elOffset;\n        }\n        if (this.isAnimateOnScroll) {\n            if (visible) {\n                const start = 100 / (parseFloat(el.dataset.scrollZoneStart) || 1);\n                const end = 100 / (parseFloat(el.dataset.scrollZoneEnd) || 1);\n                const out = el.classList.contains(\"o_animate_out\");\n                const ratio =\n                    (out ? elTop + elHeight : elTop) / (windowsHeight - windowsHeight / start);\n                const duration = parseFloat(window.getComputedStyle(el).animationDuration);\n                const delay = (ratio - 1) * (duration * end);\n                this.delay = (out ? -duration - delay : delay) + \"s\";\n                this.isAnimating = true;\n            } else if (el.classList.contains(\"o_animating\")) {\n                this.isAnimating = false;\n            }\n        } else {\n            if (visible && this.playState === \"paused\") {\n                el.classList.add(\"o_visible\");\n                this.startAnimation();\n            } else if (\n                !visible &&\n                el.classList.contains(\"o_animate_both_scroll\") &&\n                this.playState === \"running\"\n            ) {\n                el.classList.remove(\"o_visible\");\n                this.resetAnimation();\n            }\n        }\n    }\n\n    updateContent() {\n        super.updateContent();\n        this.el.dispatchEvent(new Event(\"updatecontent\", { bubbles: true }));\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.animation\", Animation);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { touching, isVisible } from \"@web/core/utils/ui\";\n\nexport class BottomFixedElement extends Interaction {\n    static selector = \"#wrapwrap\";\n    dynamicContent = {\n        _window: {\n            \"t-on-resize\": this.hideBottomFixedElements,\n            \"t-on-scroll\": this.hideBottomFixedElements,\n        },\n    };\n\n    destroy() {\n        this.restoreBottomFixedElements();\n    }\n\n    hideBottomFixedElements() {\n        // Note: check in the whole DOM instead of #wrapwrap as unfortunately\n        // some things are still put outside of the #wrapwrap (like the livechat\n        // button which is the main reason of this code).\n        const bottomFixedEls = document.querySelectorAll(\".o_bottom_fixed_element\");\n        if (!bottomFixedEls.length) {\n            return;\n        }\n\n        // The bottom fixed elements are always hidden when a modal is open\n        // thanks to the CSS that is based on the 'modal-open' class added to\n        // the body. However, when the modal does not have a backdrop (e.g.\n        // cookies bar), this 'modal-open' class is not added. That's why we\n        // handle it here. Note that the popup widget code triggers a 'scroll'\n        // event when the modal is hidden to make the bottom fixed elements\n        // reappear.\n        if (this.el.querySelector(\".s_popup_no_backdrop.show\")) {\n            for (const bottomFixedEl of bottomFixedEls) {\n                bottomFixedEl.classList.add(\"o_bottom_fixed_element_hidden\");\n            }\n            return;\n        }\n\n        this.restoreBottomFixedElements();\n\n        if (\n            document.scrollingElement.offsetHeight + document.scrollingElement.scrollTop >=\n            document.scrollingElement.scrollHeight - 2\n        ) {\n            const buttonEls = [...this.el.querySelectorAll(\"a, .btn\")].filter(isVisible);\n            for (const bottomFixedEl of bottomFixedEls) {\n                const bcr = bottomFixedEl.getBoundingClientRect();\n                const touchingButtonEl = touching(buttonEls, {\n                    top: bcr.top,\n                    right: bcr.right,\n                    bottom: bcr.bottom,\n                    left: bcr.left,\n                    width: bcr.width,\n                    height: bcr.height,\n                    x: bcr.x,\n                    y: bcr.y,\n                });\n                if (touchingButtonEl.length) {\n                    if (bottomFixedEl.classList.contains(\"o_bottom_fixed_element_move_up\")) {\n                        bottomFixedEl.style.marginBottom =\n                            window.innerHeight -\n                            touchingButtonEl.getBoundingClientRect().top +\n                            5 +\n                            \"px\";\n                    } else {\n                        bottomFixedEl.classList.add(\"o_bottom_fixed_element_hidden\");\n                    }\n                }\n            }\n        }\n    }\n\n    restoreBottomFixedElements() {\n        const bottomFixedEls = this.el.querySelectorAll(\".o_bottom_fixed_element\");\n        for (const bottomFixedEl of bottomFixedEls) {\n            bottomFixedEl.classList.remove(\"o_bottom_fixed_element_hidden\");\n            if (bottomFixedEl.classList.contains(\"o_bottom_fixed_element_move_up\")) {\n                bottomFixedEl.style.marginBottom = \"\";\n            }\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.bottom_fixed_element\", BottomFixedElement);\n\nregistry.category(\"public.interactions.edit\").add(\"website.bottom_fixed_element\", {\n    Interaction: BottomFixedElement,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * This class is used to fix carousel auto-slide behavior in Odoo 17.4 and up.\n * It handles upgrade cases from lower versions.\n * TODO find a way to get rid of this with an upgrade script?\n */\nexport class CarouselBootstrapUpgradeFix extends Interaction {\n    // Only consider our known carousel snippets. A bootstrap carousel could\n    // have been added in an embed code snippet, or in any custom snippet. In\n    // that case, we consider that it should use the new default BS behavior,\n    // assuming the user / the developer of the custo should have updated the\n    // behavior as wanted themselves.\n    // Note: dynamic snippets are handled separately (TODO review).\n    static selector = [\n        \"[data-snippet='s_image_gallery'] .carousel\",\n        \"[data-snippet='s_carousel'] .carousel\",\n        \"[data-snippet='s_quotes_carousel'] .carousel\",\n        \"[data-snippet='s_quotes_carousel_minimal'] .carousel\",\n        \"[data-snippet='s_carousel_intro'] .carousel\",\n        \"#o-carousel-product.carousel\",\n    ].join(\", \");\n    dynamicContent = {\n        _root: {\n            \"t-on-slide.bs.carousel\": () => (this.sliding = true),\n            \"t-on-slid.bs.carousel\": () => (this.sliding = false),\n            \"t-att-class\": () => ({\n                o_carousel_sliding: this.sliding,\n            }),\n        },\n    };\n    carouselOptions = undefined;\n\n    setup() {\n        this.sliding = false;\n        this.hasInterval = ![undefined, \"false\", \"0\"].includes(this.el.dataset.bsInterval);\n    }\n\n    async willStart() {\n        if (this.hasInterval || this.el.dataset.bsRide) {\n            // Wait for carousel to finish sliding.\n            if (this.el.classList.contains(\"o_carousel_sliding\")) {\n                await new Promise((resolve) => {\n                    this.addListener(this.el, \"slid.bs.carousel\", () => resolve(), { once: true });\n                });\n            }\n            window.Carousel.getInstance(this.el)?.dispose();\n        }\n    }\n\n    start() {\n        if (this.hasInterval || this.el.dataset.bsRide) {\n            // Respawn carousel.\n            const carousel = window.Carousel.getOrCreateInstance(this.el, this.carouselOptions);\n            this.registerCleanup(() => carousel.dispose());\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.carousel_bootstrap_upgrade_fix\", CarouselBootstrapUpgradeFix);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport { onceAllImagesLoaded } from \"@website/utils/images\";\n\nexport class CarouselSlider extends Interaction {\n    static selector = \".carousel\";\n    dynamicContent = {\n        _root: {\n            \"t-on-slide.bs.carousel\": this.onSlideCarousel,\n            \"t-on-slid.bs.carousel\": this.onSlidCarousel,\n        },\n        img: {\n            \"t-on-load\": this.computeMaxHeight,\n        },\n        _window: {\n            \"t-on-resize\": this.debounced(this.computeMaxHeight, 250),\n        },\n        \".carousel-item\": {\n            \"t-att-style\": () => ({\n                \"min-height\": this.maxHeight ? `${this.maxHeight}px` : \"\",\n            }),\n        },\n        \".slide-link\": { \"t-att-class\": () => ({ \"d-none\": !this.showClickableSlideLinks }) },\n    };\n    carouselOptions = undefined;\n    showClickableSlideLinks = true;\n\n    setup() {\n        this.maxHeight = undefined;\n        this.hasInterval = ![undefined, \"false\", \"0\"].includes(this.el.dataset.bsInterval);\n        if (![\"true\", \"carousel\", \"false\"].includes(this.el.dataset.bsRide)) {\n            this.el.dataset.bsRide = this.hasInterval ? \"carousel\" : \"false\";\n        }\n        if (this.el.dataset.bsRide === \"false\") {\n            window.Carousel.getOrCreateInstance(this.el, { ride: false, pause: true });\n        } else if (!this.hasInterval) {\n            this.el.dataset.bsInterval = \"1000\";\n        }\n    }\n\n    start() {\n        this.computeMaxHeight();\n        this.updateContent();\n        const carouselBS = window.Carousel.getOrCreateInstance(this.el, this.carouselOptions);\n        this.registerCleanup(() => carouselBS.dispose());\n\n        this.carouselInnerEl = this.el.querySelector(\".carousel-inner\");\n\n        const itemWidth = getComputedStyle(this.el).getPropertyValue(\n            \"--o-carousel-item-width-percentage\"\n        );\n        const itemsPerSlide = itemWidth ? Math.round(100 / parseFloat(itemWidth)) : 1;\n        this.options = {\n            scrollMode: this.el.classList.contains(\"o_carousel_multi_items\") ? \"single\" : \"all\",\n            itemsPerSlide: itemsPerSlide,\n        };\n\n        // Preload first items only when carousel is on screen\n        const observer = new IntersectionObserver((entries) => {\n            entries.forEach((entry) => {\n                if (entry.isIntersecting) {\n                    this.loadItemsToAppear();\n                    observer.unobserve(this.el);\n                }\n            });\n        });\n        observer.observe(this.el);\n    }\n\n    computeMaxHeight() {\n        this.maxHeight = undefined;\n        // \"updateContent()\" is necessary to reset the min-height before the\n        // following check.\n        this.updateContent();\n        for (const itemEl of this.el.querySelectorAll(\".carousel-item\")) {\n            const isActive = itemEl.classList.contains(\"active\");\n            itemEl.classList.add(\"active\");\n            const height = itemEl.getBoundingClientRect().height;\n            if (height > this.maxHeight || this.maxHeight === undefined) {\n                this.maxHeight = height;\n            }\n            itemEl.classList.toggle(\"active\", isActive);\n        }\n    }\n\n    /**\n     * Handles the 'slide' event of the carousel. Called *before* a slide\n     * transition.\n     *\n     * @param {Event} ev The Bootstrap Carousel slide event.\n     */\n    onSlideCarousel(ev) {\n        const imageEls = [...this.carouselInnerEl.querySelectorAll(\"img\")];\n        const isLoading = imageEls.some((el) => el.loading !== \"lazy\" && !el.complete);\n        if (isLoading) {\n            // If images are loading, prevent the slide transition. It will\n            // slide once the next images are loaded.\n            ev.preventDefault();\n            onceAllImagesLoaded(this.carouselInnerEl).then(() => {\n                window.Carousel.getOrCreateInstance(this.el).to(ev.to);\n            });\n            return;\n        }\n        if (this.options.scrollMode === \"single\") {\n            this.onSlideSingleScroll(ev);\n        }\n    }\n\n    /**\n     * Handles the 'slid' event of the carousel. Called *after* a slide\n     * transition.\n     *\n     * @param {Event} ev The Bootstrap Carousel slid event.\n     */\n    onSlidCarousel(ev) {\n        if (this.options.scrollMode === \"single\") {\n            this.onSlidSingleScroll(ev);\n        }\n        this.loadItemsToAppear(); // Preload future items after a slide\n    }\n\n    /**\n     * Manages multi-items single-scroll behavior during the 'slide' event.\n     * Prepares the DOM for smooth transitions by moving elements.\n     *\n     * @param {Event} ev The Bootstrap Carousel slide event.\n     */\n    onSlideSingleScroll(ev) {\n        // We need to keep the active element at the beginning of the carousel-items elements\n        // This allows to have a smooth transition when the carousel is sliding\n        if (ev.direction === \"right\") {\n            const carouselItemsEls = Array.from(\n                this.carouselInnerEl.querySelectorAll(\".carousel-item\")\n            );\n            this.carouselInnerEl.prepend(carouselItemsEls.pop());\n        }\n    }\n\n    /**\n     * Manages single-item scroll behavior during the 'slid' event.\n     * Completes the DOM manipulation started in `onSlideSingleScroll`.\n     *\n     * @param {Event} ev The Bootstrap Carousel slid event.\n     */\n    onSlidSingleScroll(ev) {\n        // As for the onSlideSingleScroll method, we need to keep the active\n        // element at the beginning of the carousel-items list in the DOM. So\n        // when animation is done, we move the first item (which is not active\n        // anymore) to the end.\n        if (ev.direction === \"left\") {\n            const carouselItemsEls = this.carouselInnerEl.querySelectorAll(\".carousel-item\");\n            this.carouselInnerEl.appendChild(carouselItemsEls[0]);\n        }\n    }\n\n    /**\n     * Loads images of the carousel-item necessary for both 'prev' and 'next'\n     * animations. Loads images for items that are about to become visible.\n     *\n     * @param {number} [nbItemsToLoad=1] The number of items to preload on each side.\n     */\n    loadItemsToAppear(nbItemsToLoad = 1) {\n        if (!this.carouselInnerEl) {\n            return;\n        }\n        const itemEls = Array.from(this.carouselInnerEl.children);\n        const index = itemEls.findIndex((el) => el.classList.contains(\"active\"));\n        const activeItemIndex = index >= 0 ? index : 0;\n\n        // Load \"Next\" items: nbItemsToLoad items after the active element\n        const nbItemElsOnScreen =\n            this.options.scrollMode === \"single\" ? this.options.itemsPerSlide + 1 : 1;\n        const nextEndIndex = Math.min(\n            activeItemIndex + nbItemElsOnScreen + nbItemsToLoad + 1,\n            itemEls.length\n        );\n        const nextItemElsToLoad = itemEls.slice(activeItemIndex, nextEndIndex);\n\n        // load \"Prev\" items : nbItemsToLoad items before the active element (circular wrapping)\n        // if currentIndex is 0, then the nbItemsToLoad items are the last elements of the carousel\n        let prevItemElsToLoad = [];\n        if (activeItemIndex - nbItemsToLoad < 0) {\n            const wrapAmount = Math.abs(activeItemIndex - nbItemsToLoad);\n            prevItemElsToLoad = itemEls\n                .slice(itemEls.length - wrapAmount, itemEls.length)\n                .reverse()\n                .concat(itemEls.slice(0, activeItemIndex).reverse());\n        } else {\n            prevItemElsToLoad = itemEls\n                .slice(Math.max(0, activeItemIndex - nbItemsToLoad), activeItemIndex)\n                .reverse();\n        }\n\n        // Set the `loading` attribute of lazy-loaded images to `eager` for the\n        // given carousel items. This forces the browser to load the images\n        // immediately.\n        const itemsToLoad = nextItemElsToLoad.concat(prevItemElsToLoad);\n        for (const carouselItemEl of itemsToLoad) {\n            const imageEls = carouselItemEl.querySelectorAll(\"img[loading='lazy']\");\n            for (const imageEl of imageEls) {\n                // Note that we remove the attribute with the goal of forcing it\n                // to the \"eager\" value. Removing the attribute is better so\n                // that the attribute is not saved as eager in edit mode (the\n                // lazy value is auto added on page rendering).\n                imageEl.removeAttribute(\"loading\");\n            }\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.carousel_slider\", CarouselSlider);\n", "import { CarouselSlider } from \"@website/interactions/carousel/carousel_slider\";\nimport { registry } from \"@web/core/registry\";\n\nconst CarouselSliderPreview = (I) =>\n    class extends I {\n        carouselOptions = { ride: true, pause: true, interval: 500 };\n\n        dynamicSelectors = {\n            ...this.dynamicSelectors,\n            _snippetPreviewWrapEl: () => this.el.closest(\".o_snippet_preview_wrap\"),\n        };\n        dynamicContent = {\n            ...this.dynamicContent,\n            // Bind events to the entire preview wrap. This ensures events\n            // trigger when hovering anywhere on the snippet, even though the\n            // preview containers are `inert`.\n            _snippetPreviewWrapEl: {\n                \"t-on-mouseenter\": this.mouseEnter,\n                \"t-on-mouseleave\": this.mouseLeave,\n                \"t-on-focusin\": this.mouseEnter,\n                \"t-on-focusout\": this.mouseLeave,\n            },\n        };\n\n        /**\n         * Starts the carousel autoplay when the mouse enters the element.\n         */\n        mouseEnter() {\n            const carousel = window.Carousel.getOrCreateInstance(this.el);\n            carousel.cycle();\n        }\n\n        /**\n         * Pauses the carousel and resets it to the first slide when the mouse\n         * leaves the element.\n         */\n        mouseLeave() {\n            const carousel = window.Carousel.getOrCreateInstance(this.el);\n            carousel.pause();\n            carousel.to(0);\n        }\n    };\n\nregistry.category(\"public.interactions.preview\").add(\"website.carousel_slider\", {\n    Interaction: CarouselSlider,\n    mixin: CarouselSliderPreview,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { MEDIAS_BREAKPOINTS, SIZES } from \"@web/core/ui/ui_service\";\n\nexport class CookiesApproval extends Interaction {\n    static selector = \"[data-need-cookies-approval]\";\n    dynamicContent = {\n        _document: {\n            \"t-on-optionalCookiesAccepted.once\": this.onOptionalCookiesAccepted,\n        },\n    };\n\n    setup() {\n        this.iframeEl = this.el.tagName === \"IFRAME\" ? this.el : this.el.querySelector(\"iframe\");\n    }\n\n    start() {\n        if (this.iframeEl && !this.getCookiesWarningEl()) {\n            this.addOptionalCookiesWarning();\n        }\n    }\n\n    getCookiesWarningEl() {\n        if (this.iframeEl.nextElementSibling?.classList.contains(\"o_no_optional_cookie\")) {\n            return this.iframeEl.nextElementSibling;\n        }\n        return null;\n    }\n\n    addOptionalCookiesWarning() {\n        this.renderAt(\n            \"website.cookiesWarning\",\n            {\n                extraStyle: this.iframeEl.parentElement.classList.contains(\"media_iframe_video\")\n                    ? `aspect-ratio: 16/9; max-width: ${MEDIAS_BREAKPOINTS[SIZES.SM].maxWidth}px;`\n                    : \"\",\n                extraClasses:\n                    getComputedStyle(this.iframeEl.parentElement).position === \"absolute\"\n                        ? \"\"\n                        : \"my-3\",\n            },\n            this.iframeEl,\n            \"afterend\"\n        );\n    }\n\n    onOptionalCookiesAccepted() {\n        delete this.el.dataset.needCookiesApproval;\n        if (this.iframeEl?.dataset.nocookieSrc) {\n            this.iframeEl.src = this.iframeEl.dataset.nocookieSrc;\n            delete this.iframeEl.dataset.nocookieSrc;\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.cookies_approval\", CookiesApproval);\n", "import { Popup } from \"@website/interactions/popup/popup\";\nimport { registry } from \"@web/core/registry\";\n\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { isVisible } from \"@web/core/utils/ui\";\nimport { cloneContentEls } from \"@website/js/utils\";\nimport { setUtmsHtmlDataset } from \"@website/utils/misc\";\n\n// Extending the Popup class with cookiebar functionality.\n// This allows for refusing optional cookies for now and can be\n// extended to picking which cookies categories are accepted.\nexport class CookiesBar extends Popup {\n    static selector = \"#website_cookies_bar\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _cookiesbus: () => this.services.website_cookies.bus,\n    };\n    dynamicContent = {\n        ...this.dynamicContent,\n        _cookiesbus: {\n            \"t-on-cookiesBar.show\": this.onShowCookiesBar,\n            \"t-on-cookiesBar.toggle\": this.onToggleCookiesBar,\n        },\n        \"#cookies-consent-essential, #cookies-consent-all\": { \"t-on-click\": this.onAcceptClick },\n        // Override to avoid side effects on hide.\n        \".js_close_popup\": { \"t-on-click\": () => {} },\n    };\n\n    setup() {\n        super.setup();\n        this.showToggle();\n    }\n\n    start() {\n        super.start();\n\n        // Add a link to the cookie policy page in the copyright footer.\n        // TODO: In master, add this link via XML.\n        const copyrightFooterContainerEl = document.querySelector(\n            \".o_footer_copyright_name\"\n        )?.parentElement;\n        if (copyrightFooterContainerEl) {\n            const cookiePolicyLinkEl = cloneContentEls(`\n                <p><a href=\"/cookie-policy\" class=\"o_cookie_policy_link\">${_t(\n                    \"Cookie Policy\"\n                )}</a></p>\n            `).firstElementChild;\n            this.insert(cookiePolicyLinkEl, copyrightFooterContainerEl);\n        }\n\n        // Since cookie preferences can be changed, update the gtag script that\n        // toggles the gtag consent. So, when the user modifies their cookie\n        // preference their gtag consent is also updated.\n        // TODO: In master, update the #tracking_code_config script via XML.\n        const originalTrackingCodeScriptEl = document.querySelector(\"#tracking_code_config\");\n        if (originalTrackingCodeScriptEl) {\n            // Remove the one-time event listener added by the original script\n            document.removeEventListener(\"optionalCookiesAccepted\", window.allConsentsGranted);\n\n            // Create a new script element\n            const updatedTrackingCodeScript = `\n                window.dataLayer = window.dataLayer || [];\n                function gtag() {\n                    dataLayer.push(arguments);\n                }\n\n                function updateConsents(consentState) {\n                    gtag(\"consent\", \"update\", {\n                        \"ad_storage\": consentState,\n                        \"ad_user_data\": consentState,\n                        \"ad_personalization\": consentState,\n                        \"analytics_storage\": consentState,\n                    });\n                }\n\n                document.addEventListener(\"optionalCookiesAccepted\", () => {\n                    updateConsents(\"granted\");\n                });\n\n                document.addEventListener(\"optionalCookiesDenied\", () => {\n                    updateConsents(\"denied\");\n                });\n            `;\n            const newScriptEl = document.createElement(\"script\");\n            newScriptEl.id = \"tracking_code_config\";\n            newScriptEl.textContent = updatedTrackingCodeScript;\n\n            // Replace the original script with the new one\n            originalTrackingCodeScriptEl.parentNode.replaceChild(\n                newScriptEl,\n                originalTrackingCodeScriptEl\n            );\n        }\n    }\n\n    showPopup() {\n        super.showPopup();\n        if (this.toggleEl) {\n            this.onToggleCookiesBar();\n        }\n    }\n\n    showToggle() {\n        const policyLinkEl = this.el.querySelector(\".o_cookies_bar_text_policy\");\n        if (policyLinkEl && window.location.pathname === new URL(policyLinkEl.href).pathname) {\n            this.toggleEl = cloneContentEls(`\n            <button class=\"o_cookies_bar_toggle btn btn-info btn-sm rounded-circle d-flex gap-2 align-items-center position-fixed pe-auto\">\n                <i class=\"fa fa-eye\" alt=\"\" aria-hidden=\"true\"></i> <span class=\"o_cookies_bar_toggle_label\"></span>\n            </button>\n            `).firstElementChild;\n            this.insert(this.toggleEl, this.el, \"beforebegin\");\n        }\n    }\n\n    onToggleCookiesBar() {\n        this.cookieValue = cookie.get(this.el.id);\n        this.bsModal.toggle();\n        // As we're using Bootstrap's events, the Popup class prevents the modal\n        // from being shown after hiding it: override that behavior.\n        this.popupAlreadyShown = false;\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onAcceptClick(ev) {\n        const isFullConsent = ev.currentTarget.id === \"cookies-consent-all\";\n        this.cookieValue = `{\"required\": true, \"optional\": ${isFullConsent}, \"ts\": ${Date.now()}}`;\n        if (isFullConsent) {\n            document.dispatchEvent(new Event(\"optionalCookiesAccepted\"));\n        } else {\n            document.dispatchEvent(new Event(\"optionalCookiesDenied\"));\n        }\n        this.bsModal.hide();\n        this.services.website_cookies.bus.trigger(\"cookiesBar.discard\");\n    }\n\n    onHideModal() {\n        super.onHideModal();\n        const params = new URLSearchParams(window.location.search);\n        const trackingFields = {\n            utm_campaign: \"odoo_utm_campaign\",\n            utm_source: \"odoo_utm_source\",\n            utm_medium: \"odoo_utm_medium\",\n        };\n        for (const [key, value] of params) {\n            if (key in trackingFields) {\n                // Using same cookie expiration value as in python side\n                cookie.set(trackingFields[key], value, 31 * 24 * 60 * 60, \"optional\");\n            }\n        }\n        setUtmsHtmlDataset();\n    }\n\n    /**\n     * Reopens the cookies bar if it was closed.\n     */\n    onShowCookiesBar() {\n        const currCookie = cookie.get(this.el.id);\n        if ((currCookie && JSON.parse(currCookie).optional) || !this.popupAlreadyShown) {\n            return;\n        }\n        this.bsModal.show();\n\n        // The cookies bar remains hidden, most probably because of the browser\n        // or an extension: notify the user because \"nothing happens when I\n        // click\" is never good.\n        if (!isVisible(this.modalEl)) {\n            window.alert(_t(\"Our cookies bar was blocked by your browser or an extension.\"));\n            return;\n        }\n        this.modalEl.focus();\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.cookies_bar\", CookiesBar);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { onceAllImagesLoaded } from \"@website/utils/images\";\n\nexport class CookiesToggle extends Interaction {\n    static selector = \".o_cookies_bar_toggle\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _cookiesbus: () => this.services.website_cookies.bus,\n    };\n    dynamicContent = {\n        _root: { \"t-on-click\": this.onClick },\n        _cookiesbus: { \"t-on-cookiesBar.discard\": this.onClick },\n        \".o_cookies_bar_toggle_label\": { \"t-out\": this.toggleText },\n        \".fa\": {\n            \"t-att-class\": () => ({\n                \"fa-eye\": !this.isModalShown(),\n                \"fa-eye-slash\": this.isModalShown(),\n            }),\n        },\n    };\n\n    setup() {\n        this.cookiesModalEl = this.el.nextElementSibling.querySelector(\".modal\");\n    }\n\n    isModalShown() {\n        return this.cookiesModalEl.classList.contains(\"show\");\n    }\n\n    toggleText() {\n        return this.isModalShown() ? _t(\"Hide the cookies bar\") : _t(\"Show the cookies bar\");\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    async onClick(ev) {\n        if (ev.currentTarget === this.el) {\n            this.services.website_cookies.bus.trigger(\"cookiesBar.toggle\");\n        }\n\n        // Changing the property cannot be done in \"t-att-style\" in\n        // dynamicContent as it relies on async code.\n        if (!this.isModalShown() || !this.cookiesModalEl.classList.contains(\"s_popup_bottom\")) {\n            this.el.style.removeProperty(\"--cookies-bar-toggle-inset-block-end\");\n        } else {\n            // Lazy-loaded images don't have a height yet. We need to await them\n            await this.waitFor(onceAllImagesLoaded(this.cookiesModalEl));\n            const popupHeight = this.cookiesModalEl.querySelector(\".modal-content\").offsetHeight;\n            const toggleMargin = 8;\n            // Avoid having the toggle over another button, but if the cookies\n            // bar is too tall, place it at the bottom anyway.\n            const bottom =\n                document.body.offsetHeight > popupHeight + this.el.offsetHeight + toggleMargin\n                    ? `calc(\n                    ${\n                        getComputedStyle(this.cookiesModalEl.querySelector(\".modal-dialog\"))\n                            .paddingBottom\n                    }\n                    + ${popupHeight + toggleMargin}px\n                )`\n                    : \"\";\n            this.el.style.setProperty(\"--cookies-bar-toggle-inset-block-end\", bottom);\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.cookies_toggle\", CookiesToggle);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class CookiesWarning extends Interaction {\n    static selector = \".o_no_optional_cookie\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _iframe: () => (this.keptIframeEl ??= this.el.previousElementSibling),\n    };\n    dynamicContent = {\n        _root: { \"t-on-click\": () => this.services.website_cookies.bus.trigger(\"cookiesBar.show\") },\n        _iframe: {\n            \"t-att-class\": () => ({\n                \"d-none\": !!this.el.parentElement,\n            }),\n        },\n        _document: {\n            \"t-on-optionalCookiesAccepted.once\": () => this.el.remove(),\n        },\n    };\n    setup() {\n        // Keeps track of the initially found iframe so that it is still known\n        // after optionalCookiesAccepted.\n        this.keptIframeEl = undefined;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.cookies_warning\", CookiesWarning);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { SIZES, utils as uiUtils } from \"@web/core/ui/ui_service\";\n\nexport class HoverableDropdown extends Interaction {\n    static selector = \"header.o_hoverable_dropdown\";\n    dynamicContent = {\n        \".dropdown\": {\n            \"t-on-mouseenter.withTarget\": this.onMouseEnter,\n            \"t-on-mouseleave.withTarget\": this.onMouseLeave,\n        },\n        \".nav:not(.o_mega_menu_is_offcanvas) .o_mega_menu\": {\n            \"t-att-style\": () => ({\n                \"margin-top\": this.isSmall() ? \"\" : \"0 !important\",\n                top: this.isSmall() ? \"\" : \"unset\",\n            }),\n        },\n        _window: {\n            \"t-on-resize\": this.onResize,\n        },\n    };\n\n    setup() {\n        this.dropdownMenuEls = this.el.querySelectorAll(\".dropdown-menu\");\n        this.breakpointSize = SIZES.LG; // maybe need to check in .navbar elem like in BaseHeader?\n    }\n\n    start() {\n        this.onResize();\n    }\n\n    isSmall() {\n        return uiUtils.getSize() < this.breakpointSize;\n    }\n\n    /**\n     * @param {Event} dropdownEl\n     * @param {boolean} show\n     */\n    updateDropdownVisibility(dropdownEl, show) {\n        const dropdownToggleEl = dropdownEl.querySelector(\".dropdown-toggle\");\n        if (this.isSmall() || !dropdownToggleEl || dropdownEl.closest(\".o_extra_menu_items\")) {\n            return;\n        }\n        const dropdown = Dropdown.getOrCreateInstance(dropdownToggleEl);\n        show ? dropdown.show() : dropdown.hide();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onMouseEnter(ev, currentTargetEl) {\n        const focusedEl =\n            this.el.ownerDocument.querySelector(\":focus\") ||\n            window.frameElement?.ownerDocument.querySelector(\":focus\");\n\n        // The user must click on the dropdown if he is on mobile (no way to\n        // hover) or if the dropdown is the (or in the) extra menu ('+').\n        this.updateDropdownVisibility(currentTargetEl, true);\n\n        // Keep the focus on the previously focused element if any, otherwise do\n        // not focus the dropdown on hover.\n        if (focusedEl) {\n            focusedEl.focus({ preventScroll: true });\n        } else {\n            const dropdownToggleEl = ev.currentTarget.querySelector(\".dropdown-toggle\");\n            if (dropdownToggleEl) {\n                dropdownToggleEl.blur();\n            }\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onMouseLeave(ev, targelEl) {\n        this.updateDropdownVisibility(targelEl, false);\n    }\n\n    onResize() {\n        for (const dropdownMenuEl of this.dropdownMenuEls) {\n            dropdownMenuEl.setAttribute(\"data-bs-popper\", \"none\");\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.hoverable_dropdown\", HoverableDropdown);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class MegaMenuDropdown extends Interaction {\n    static selector = \"header#top\";\n    dynamicContent = {\n        \".o_mega_menu_toggle\": {\n            \"t-on-mouseenter.withTarget\": this.onHoverMegaMenu,\n            \"t-on-mousedown.withTarget\": this.onTriggerMegaMenu,\n            \"t-on-keyup.withTarget\": this.onTriggerMegaMenu,\n        },\n        _root: {\n            \"t-on-mousedown\": this.onTriggerExtraMenu, // delegated to \".o_extra_menu_items\"\n            \"t-on-keyup\": this.onTriggerExtraMenu, // delegated to \".o_extra_menu_items\"\n        },\n    };\n\n    setup() {\n        this.mobileMegaMenuToggleEls = [];\n        this.desktopMegaMenuToggleEls = [];\n        const megaMenuToggleEls = this.el.querySelectorAll(\".o_mega_menu_toggle\");\n        for (const megaMenuToggleEl of megaMenuToggleEls) {\n            if (megaMenuToggleEl.closest(\".o_header_mobile\")) {\n                this.mobileMegaMenuToggleEls.push(megaMenuToggleEl);\n            } else {\n                this.desktopMegaMenuToggleEls.push(megaMenuToggleEl);\n            }\n        }\n        this.updateActiveMenuLinks();\n    }\n\n    updateActiveMenuLinks() {\n        // Prevent having several active links in the menu.\n        if (this.el.querySelector(\".navbar #top_menu a.nav-link.active\")) {\n            return;\n        }\n        const currentHrefWithoutHash = `${window.location.origin}${window.location.pathname}`;\n        // Check and update the active state of menu items based on the current\n        // page\n        const megaMenuEls = this.el.querySelectorAll(\".o_mega_menu\");\n        let matchingLink = null;\n        megaMenuEls.forEach((megaMenuEl, position) => {\n            const linkEls = Array.from(megaMenuEl.querySelectorAll(`a[href]:not([href=\"#\"])`));\n            matchingLink = linkEls.find((linkEl) => {\n                try {\n                    const url = new URL(linkEl.href);\n                    return `${url.origin}${url.pathname}` === currentHrefWithoutHash;\n                } catch {\n                    return false;\n                }\n            });\n            if (matchingLink) {\n                const megaMenuToggleEl = megaMenuEl\n                    .closest(\".nav-item\")\n                    .querySelector(\".o_mega_menu_toggle\");\n                // Target the corresponding link in the mobile navigation. Since the\n                // mega-menu for mobile is dynamically rendered, it is not\n                // accessible at this moment.\n                const mobileMegaMenuToggleEl = this.el.querySelectorAll(\n                    \"#top_menu_collapse_mobile .top_menu .o_mega_menu_toggle\"\n                )[position];\n                megaMenuToggleEl.classList.add(\"active\");\n                mobileMegaMenuToggleEl.classList.add(\"active\");\n            }\n        });\n    }\n\n    /**\n     * If the mega menu dropdown on which we are clicking/hovering does not have\n     * a mega menu (i.e. it is in the other navbar), brings the corresponding\n     * mega menu into it.\n     *\n     * @param {HTMLElement} megaMenuToggleEl\n     */\n    moveMegaMenu(megaMenuToggleEl) {\n        const hasMegaMenu = !!megaMenuToggleEl.parentElement.querySelector(\".o_mega_menu\");\n        if (hasMegaMenu) {\n            return;\n        }\n        const isMobileNavbar = !!megaMenuToggleEl.closest(\".o_header_mobile\");\n        const currentNavbarToggleEls = isMobileNavbar\n            ? this.mobileMegaMenuToggleEls\n            : this.desktopMegaMenuToggleEls;\n        const otherNavbarToggleEls = isMobileNavbar\n            ? this.desktopMegaMenuToggleEls\n            : this.mobileMegaMenuToggleEls;\n\n        const megaMenuToggleIndex = currentNavbarToggleEls.indexOf(megaMenuToggleEl);\n        const previousMegaMenuToggleEl = otherNavbarToggleEls[megaMenuToggleIndex];\n        const megaMenuEl = previousMegaMenuToggleEl.parentElement.querySelector(\".o_mega_menu\");\n\n        // Hiding the dropdown where the mega menu comes from before moving it,\n        // so everything is in a consistent state.\n        Dropdown.getOrCreateInstance(previousMegaMenuToggleEl).hide();\n        megaMenuToggleEl.insertAdjacentElement(\"afterend\", megaMenuEl);\n    }\n\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onTriggerMegaMenu(ev, currentTargetEl) {\n        // Hoverable menus are clicked in mobile view\n        if (\n            this.el.classList.contains(\"o_hoverable_dropdown\") &&\n            !currentTargetEl.closest(\".o_header_mobile\") &&\n            ev.type !== \"keyup\"\n        ) {\n            return;\n        }\n        this.moveMegaMenu(currentTargetEl);\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onHoverMegaMenu(ev, currentTargetEl) {\n        // Hoverable menus are clicked in mobile view\n        if (\n            !this.el.classList.contains(\"o_hoverable_dropdown\") ||\n            currentTargetEl.closest(\".o_header_mobile\")\n        ) {\n            return;\n        }\n        this.moveMegaMenu(currentTargetEl);\n    }\n\n    /**\n     * Delegatation to the \".o_extra_menu_items\" element(s)\n     * The \".o_extra_menu_items\" elements may not be on the page at all time\n     *\n     * @param {Event} ev\n     */\n    onTriggerExtraMenu(ev) {\n        if (!ev.target.closest(\".o_extra_menu_items\")) {\n            return;\n        }\n        const megaMenuToggleEls = ev.target\n            .closest(\".o_extra_menu_items\")\n            .querySelectorAll(\".o_mega_menu_toggle\");\n        megaMenuToggleEls.forEach((el) => this.moveMegaMenu(el));\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.mega_menu_dropdown\", MegaMenuDropdown);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class FooterSlideout extends Interaction {\n    static selector = \"#wrapwrap\";\n    static selectorHas = \".o_footer_slideout\";\n\n    start() {\n        // On safari, add a pixel div over the footer, after in the DOM, and add\n        // a background attachment on it as it fixes the glitches that appear\n        // when scrolling the page with a footer slide out.\n        // TODO check if the hack is still needed (might have been fixed when\n        // the scrollbar was restored to its natural position).\n        if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n            const pixelEl = document.createElement(\"div\");\n            pixelEl.style.width = \"1px\";\n            pixelEl.style.height = \"1px\";\n            pixelEl.style.marginTop = \"-1px\";\n            pixelEl.style.backgroundColor = \"transparent\";\n            pixelEl.style.backgroundAttachment = \"fixed\";\n            pixelEl.style.backgroundImage = \"url(/website/static/src/img/website_logo.svg)\";\n            this.insert(pixelEl);\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.footer_slideout\", FooterSlideout);\n\nregistry.category(\"public.interactions.edit\").add(\"website.footer_slideout\", {\n    Interaction: FooterSlideout,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { isVisible } from \"@web/core/utils/ui\";\n\nexport class FullScreenHeight extends Interaction {\n    static selector = \".o_full_screen_height\";\n    dynamicContent = {\n        _window: {\n            \"t-on-resize.noUpdate\": this.debounced(this.updateContent, 250, {\n                leading: true,\n                trailing: true,\n            }),\n        },\n        _root: {\n            \"t-att-style\": () => ({\n                \"min-height\": this.isActive\n                    ? `${this.computeIdealHeight()}px !important`\n                    : undefined,\n            }),\n        },\n    };\n\n    setup() {\n        this.inModal = !!this.el.closest(\".modal\");\n        const currentHeight = this.el.getBoundingClientRect().height;\n        const idealHeight = this.computeIdealHeight();\n        // Only initialize if taller than the ideal height as some extra css\n        // rules may alter the full-screen-height class behavior in some\n        // cases (blog...).\n        this.isActive = !isVisible(this.el) || currentHeight > idealHeight + 1;\n    }\n\n    computeIdealHeight() {\n        // Compute the smallest viewport height (svh) to use to set up the ideal\n        // height of the element, which won't flicker based on the viewport\n        // resize in mobile (when its browser UI changes).\n        // TODO: should try to use svh directly, combined with `calc` and\n        // CSS variables to avoid this JS as much as possible... but see below:\n        // can't because of Arc browser).\n        const viewportWidth = window.innerWidth;\n        const viewportHeight = window.innerHeight;\n        if (\n            !this.smallestViewportHeight ||\n            // Update svh definition only if the viewport resize seems to\n            // not be to a mobile browser UI change (e.g. Arc browser\n            // mistakenly changes svh while its UI changes at the moment).\n            Math.abs(viewportWidth - this.previousViewportWidth) > 15 ||\n            Math.abs(viewportHeight - this.previousViewportHeight) > 150\n        ) {\n            this.previousViewportWidth = viewportWidth;\n            this.previousViewportHeight = viewportHeight;\n            const el = document.createElement(\"div\");\n            el.classList.add(\"vh-100\");\n            el.style.position = \"fixed\";\n            el.style.top = \"0\";\n            el.style.pointerEvents = \"none\";\n            el.style.visibility = \"hidden\";\n            el.style.setProperty(\"height\", \"100svh\", \"important\");\n            document.body.appendChild(el);\n            this.smallestViewportHeight = parseFloat(el.getBoundingClientRect().height);\n            document.body.removeChild(el);\n        }\n\n        if (this.inModal) {\n            return this.smallestViewportHeight;\n        }\n\n        // Doing it that way allows to consider fixed headers, hidden headers,\n        // connected users, ...\n        const firstContentEl = this.el.ownerDocument.querySelector(\n            \"#wrapwrap > main > :first-child\"\n        ); // first child to consider the padding-top of main\n        const mainTopPos =\n            firstContentEl.getBoundingClientRect().top +\n            this.el.ownerDocument.documentElement.scrollTop;\n        return this.smallestViewportHeight - mainTopPos;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.full_screen_height\", FullScreenHeight);\n", "import { Interaction } from \"@web/public/interaction\";\n\nimport { SIZES, utils as uiUtils } from \"@web/core/ui/ui_service\";\nimport { compensateScrollbar } from \"@web/core/utils/scrolling\";\n\nexport class BaseHeader extends Interaction {\n    dynamicContent = {\n        _document: {\n            \"t-on-scroll\": this.onScroll,\n        },\n        _window: {\n            \"t-on-resize\": this.onResize,\n        },\n        _body: {\n            \"t-att-class\": () => ({\n                \"overflow-hidden\": this.bodyNoScroll,\n            }),\n        },\n        _root: {\n            \"t-on-transitionend\": () => this.adaptToHeaderChangeLoop(-1),\n            \"t-att-class\": () => ({\n                o_top_fixed_element: this.isVisible,\n                o_header_affixed: this.cssAffixed,\n                o_header_is_scrolled: this.isScrolled,\n                o_header_no_transition: !this.transitionActive,\n            }),\n        },\n        \".offcanvas\": {\n            \"t-on-show.bs.offcanvas\": this.disableScroll,\n            \"t-on-hide.bs.offcanvas\": this.enableScroll,\n        },\n        // Compatibility: can probably be removed, there is no such elements in\n        // default navbars... although it could be used by custo.\n        \".navbar-collapse\": {\n            \"t-on-show.bs.collapse\": this.disableScroll,\n            \"t-on-hide.bs.collapse\": this.enableScroll,\n        },\n    };\n\n    //--------------------------------------------------------------\n    // Life Cycle\n    //--------------------------------------------------------------\n\n    setup() {\n        this.topGap = 0;\n        this.atTop = false;\n\n        this.cssAffixed = false;\n        this.bodyNoScroll = false;\n\n        this.transitionCount = 0;\n        this.transitionActive = true;\n\n        this.isVisible = true;\n        this.isScrolled = false;\n        this.forcedScroll = 0;\n\n        this.isOverlay = !!this.el.closest(\".o_header_overlay, .o_header_overlay_theme\");\n\n        this.mainEl = this.el.parentElement.querySelector(\"main\");\n        this.hideEl = this.el.querySelector(\".o_header_hide_on_scroll\");\n        this.hideElHeight = this.hideEl?.getBoundingClientRect().height;\n\n        this.scrollingElement = document.scrollingElement;\n        const navbarEl = this.el.querySelector(\".navbar\");\n        const navBreakpoint = navbarEl\n            ? Object.keys(SIZES).find((size) =>\n                  navbarEl.classList.contains(`navbar-expand-${size.toLowerCase()}`)\n              )\n            : \"LG\";\n        this.breakpointSize = SIZES[navBreakpoint];\n\n        this.hasScrolled = false;\n        this.closeDropdowns = false;\n    }\n\n    start() {\n        this.services.website_menus.triggerCallbacks();\n        if (this.scrollingElement.scrollTop > 0) {\n            this.adjustPosition();\n        }\n    }\n\n    isSmall() {\n        return uiUtils.getSize() < this.breakpointSize;\n    }\n\n    //--------------------------------------------------------------\n    // Event Handlers\n    //--------------------------------------------------------------\n\n    disableScroll() {\n        if (this.isSmall()) {\n            this.bodyNoScroll = true;\n        }\n    }\n\n    enableScroll() {\n        this.bodyNoScroll = false;\n    }\n\n    onResize() {\n        this.adjustScrollbar();\n        if (document.body.classList.contains(\"overflow-hidden\") && !this.isSmall()) {\n            const offCanvasEls = this.el.querySelectorAll(\".offcanvas.show\");\n            for (const offCanvasEl of offCanvasEls) {\n                Offcanvas.getOrCreateInstance(offCanvasEl).hide();\n            }\n            // Compatibility: can probably be removed, there is no such elements in\n            // default navbars... although it could be used by custo.\n            const collapseEls = this.el.querySelectorAll(\".navbar-collapse.show\");\n            for (const collapseEl of collapseEls) {\n                Collapse.getOrCreateInstance(collapseEl).hide();\n            }\n        } else {\n            this.adjustMainPadding();\n        }\n    }\n\n    onScroll() {\n        const scroll = this.scrollingElement.scrollTop;\n\n        // Disable css transition if refresh with scrollTop > 0\n        if (!this.hasScrolled) {\n            this.hasScrolled = true;\n            if (scroll > 0) {\n                this.el.classList.add(\"o_header_no_transition\");\n            }\n        } else {\n            this.el.classList.remove(\"o_header_no_transition\");\n            this.closeDropdowns = true;\n        }\n\n        if (this.closeDropdowns) {\n            this.el.querySelectorAll(\".dropdown-toggle.show\").forEach((dropdownToggleEl) => {\n                Dropdown.getOrCreateInstance(dropdownToggleEl).hide();\n            });\n        }\n    }\n\n    //--------------------------------------------------------------\n    // Animation Handlers\n    //--------------------------------------------------------------\n\n    adaptToHeaderChange() {\n        this.services.website_menus.triggerCallbacks();\n        this.adjustMainPadding();\n    }\n\n    /**\n     * @param {number} [addCount=0]\n     */\n    adaptToHeaderChangeLoop(addCount = 0) {\n        this.adaptToHeaderChange();\n        this.transitionCount = Math.max(0, this.transitionCount + addCount);\n\n        // As long as we detected a transition start without its related\n        // transition end, keep updating the main padding top.\n        if (this.transitionCount > 0) {\n            this.el.classList.add(\"o_transitioning\");\n            this.waitForAnimationFrame(() => this.adaptToHeaderChangeLoop());\n\n            // The normal case would be to have the transitionend event to be\n            // fired but we cannot rely on it, so we use a timeout as fallback.\n            if (addCount !== 0) {\n                clearTimeout(this.changeLoopTimer);\n                this.changeLoopTimer = this.waitForTimeout(\n                    () => this.adaptToHeaderChangeLoop(-this.transitionCount),\n                    500\n                );\n            }\n        } else {\n            // When we detected all transitionend events, we need to stop the\n            // setTimeout fallback.\n            this.el.classList.remove(\"o_transitioning\");\n            clearTimeout(this.changeLoopTimer);\n        }\n    }\n\n    //--------------------------------------------------------------\n    // Animation Trigger\n    //--------------------------------------------------------------\n\n    transformShow() {\n        this.isVisible = true;\n        this.el.style.transform = this.atTop\n            ? \"\"\n            : `translate(0, -${this.forcedScroll + this.topGap}px)`;\n        this.adaptToHeaderChangeLoop(1);\n    }\n\n    transformHide() {\n        this.isVisible = false;\n        this.el.style.transform = \"translate(0, -100%)\";\n        this.adaptToHeaderChangeLoop(1);\n    }\n\n    //--------------------------------------------------------------\n    // Change Handlers\n    //--------------------------------------------------------------\n\n    adjustPosition() {\n        // When the url contains #aRandomSection, prevent the navbar to overlap\n        // on the section, for this, we scroll as many px as the navbar height.\n        this.scrollingElement.scrollBy(0, -this.el.offsetHeight);\n    }\n\n    adjustScrollbar() {\n        compensateScrollbar(this.el, this.cssAffixed, false, \"right\");\n    }\n\n    adjustMainPadding() {\n        if (this.isOverlay) {\n            return;\n        }\n        this.mainEl.style.setProperty(\n            \"padding-top\",\n            this.cssAffixed ? this.getHeaderHeight() + \"px\" : \"\"\n        );\n    }\n\n    //--------------------------------------------------------------\n    // Utils\n    //--------------------------------------------------------------\n\n    getHeaderHeight() {\n        return this.el.getBoundingClientRect().height;\n    }\n\n    /**\n     * @param {boolean} useAffixed\n     */\n    toggleCSSAffixed(useAffixed) {\n        this.cssAffixed = useAffixed;\n        this.adaptToHeaderChange();\n        this.adjustScrollbar();\n    }\n}\n", "import { BaseHeader } from \"@website/interactions/header/base_header\";\nimport { registry } from \"@web/core/registry\";\n\nexport class BaseHeaderSpecial extends BaseHeader {\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _searchbar: () => this.searchbarEl,\n    };\n    dynamicContent = {\n        ...this.dynamicContent,\n        \".o_header_hide_on_scroll .dropdown-toggle\": {\n            \"t-on-show.bs.dropdown\": this.onDropdownShow,\n        },\n        _searchbar: {\n            \"t-on-input\": this.onSearchbarInput,\n        },\n    };\n\n    setup() {\n        super.setup();\n        this.isAnimated = false;\n\n        this.position = 0;\n        this.checkpoint = 0;\n        this.scrollOffset = 200;\n        this.scrollingDownward = true;\n\n        this.searchbarEl = this.hideEl?.querySelector(\":not(.modal-content) > .o_searchbar_form\");\n        this.dropdownClickedEl = null;\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    onDropdownShow(ev) {\n        // If a dropdown inside the element 'this.hideEl' is clicked while the\n        // header is fixed, we need to scroll the page up so that the\n        // 'this.hideEl' element is no longer overflow hidden. Without\n        // this, the dropdown would be invisible.\n        if (this.cssAffixed) {\n            ev.preventDefault();\n            this.scrollingElement.scrollTo({ top: 0, behavior: \"smooth\" });\n            this.dropdownClickedEl = ev.currentTarget;\n        }\n    }\n\n    onSearchbarInput() {\n        // Prevents the dropdown with search results from being hidden when the\n        // header is fixed.\n        // The scroll animation is instantaneous because the dropdown could open\n        // before reaching the top of the page, which would result in an\n        // incorrect calculated height of the header.\n        if (this.cssAffixed) {\n            this.scrollingElement.scroll({ top: 0 });\n        }\n    }\n\n    onScroll() {\n        super.onScroll();\n\n        const scroll = this.scrollingElement.scrollTop;\n\n        this.atTop = scroll <= this.topGap;\n        this.isScrolled = scroll > this.topGap;\n\n        // Need to be 'unfixed' when the window is not scrolled so that the\n        // transparent menu option still works.\n        if (scroll > this.topGap) {\n            if (!this.cssAffixed) {\n                this.transformShow();\n                void this.el.offsetWidth; // Force a paint refresh\n                this.toggleCSSAffixed(true);\n            }\n        } else {\n            this.transformShow();\n            void this.el.offsetWidth; // Force a paint refresh\n            this.toggleCSSAffixed(false);\n        }\n\n        if (this.hideEl) {\n            this.hideEl.style.height = \"\";\n            this.hideEl.classList.remove(\"hidden\");\n            let elHeight = 0;\n            if (this.cssAffixed) {\n                // Close the dropdowns if they are open when scrolling.\n                // Otherwise, the calculated height of the 'hideEl' element will\n                // be incorrect because it will include the dropdown height.\n                this.hideEl\n                    .querySelectorAll(\".dropdown-toggle.show\")\n                    .forEach((dropdownToggleEl) => {\n                        Dropdown.getOrCreateInstance(dropdownToggleEl).hide();\n                    });\n                elHeight = this.hideEl.offsetHeight;\n            } else {\n                elHeight = this.hideEl.scrollHeight;\n            }\n            const scrollDelta = window.matchMedia(`(prefers-reduced-motion: reduce)`).matches\n                ? scroll\n                : Math.floor(scroll / 4);\n            elHeight = Math.max(0, elHeight - scrollDelta);\n            this.hideEl.classList.toggle(\"hidden\", elHeight === 0);\n            if (elHeight === 0) {\n                this.hideEl.removeAttribute(\"style\");\n            } else {\n                // When the page hasn't been scrolled yet, we don't set overflow\n                // to hidden. Without this, the dropdowns would be invisible.\n                // (e.g., \"user menu\" dropdown).\n                this.hideEl.style.overflow = this.cssAffixed ? \"hidden\" : \"\";\n                this.hideEl.style.height = this.cssAffixed ? `${elHeight}px` : \"\";\n                let elPadding = parseInt(getComputedStyle(this.hideEl).paddingBlock);\n                if (elHeight < elPadding * 2) {\n                    const heightDifference = elPadding * 2 - elHeight;\n                    elPadding = Math.max(0, elPadding - Math.floor(heightDifference / 2));\n                    this.hideEl.style.setProperty(\"padding-block\", `${elPadding}px`, \"important\");\n                } else {\n                    this.hideEl.style.paddingBlock = \"\";\n                }\n            }\n            this.adaptToHeaderChange();\n        }\n\n        if (!this.cssAffixed && this.dropdownClickedEl) {\n            const dropdown = Dropdown.getOrCreateInstance(this.dropdownClickedEl);\n            dropdown.show();\n            this.dropdownClickedEl = null;\n        }\n\n        if (this.isAnimated && this.transitionActive) {\n            const scrollingDownward = scroll > this.position;\n            this.position = scroll;\n            if (this.scrollingDownward !== scrollingDownward) {\n                this.checkpoint = scroll;\n            }\n            this.scrollingDownward = scrollingDownward;\n\n            if (scrollingDownward) {\n                const movement = this.position - this.checkpoint;\n                if (this.isVisible && movement > this.scrollOffset + this.topGap) {\n                    this.transformHide();\n                }\n            } else {\n                const movement = this.checkpoint - this.position;\n                if (!this.isVisible && movement > (this.scrollOffset + this.topGap) / 2) {\n                    this.transformShow();\n                }\n            }\n        }\n    }\n}\n\nregistry.category(\"public.interactions.edit\").add(\"website.base_header_special\", {\n    Interaction: BaseHeaderSpecial,\n    isAbstract: true,\n});\n", "import { BaseHeaderSpecial } from \"@website/interactions/header/base_header_special\";\nimport { registry } from \"@web/core/registry\";\n\nexport class HeaderDisappears extends BaseHeaderSpecial {\n    static selector = \"header.o_header_disappears:not(.o_header_sidebar)\";\n\n    setup() {\n        super.setup();\n        this.isAnimated = true;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.header_disappears\", HeaderDisappears);\n\nregistry.category(\"public.interactions.edit\").add(\"website.header_disappears\", {\n    Interaction: HeaderDisappears,\n});\n", "import { BaseHeaderSpecial } from \"@website/interactions/header/base_header_special\";\nimport { registry } from \"@web/core/registry\";\n\nexport class HeaderFadeOut extends BaseHeaderSpecial {\n    static selector = \"header.o_header_fade_out:not(.o_header_sidebar)\";\n\n    setup() {\n        super.setup();\n        this.isAnimated = true;\n        this.el.style.transitionDuration = \"400ms\";\n        this.el.style.transitionProperty = \"opacity\";\n        this.fadeTimeout = null;\n    }\n\n    transformShow() {\n        this.el.style.opacity = 1;\n        if (this.fadeTimeout) {\n            clearTimeout(this.fadeTimeout);\n        }\n        super.transformShow();\n    }\n\n    transformHide() {\n        this.el.style.opacity = 0;\n        this.isVisible = false;\n        // We want to translate the header after the transition is complete\n        this.fadeTimeout = this.waitForTimeout(\n            () => (this.el.style.transform = \"translate(0, -100%)\"),\n            400\n        );\n        this.adaptToHeaderChangeLoop(1);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.header_fade_out\", HeaderFadeOut);\n\nregistry.category(\"public.interactions.edit\").add(\"website.header_fade_out\", {\n    Interaction: HeaderFadeOut,\n});\n", "import { BaseHeaderSpecial } from \"@website/interactions/header/base_header_special\";\nimport { registry } from \"@web/core/registry\";\n\nexport class HeaderFixed extends BaseHeaderSpecial {\n    static selector = \"header.o_header_fixed:not(.o_header_sidebar)\";\n}\n\nregistry.category(\"public.interactions\").add(\"website.header_fixed\", HeaderFixed);\n\nregistry.category(\"public.interactions.edit\").add(\"website.header_fixed\", {\n    Interaction: HeaderFixed,\n});\n", "import { BaseHeader } from \"@website/interactions/header/base_header\";\nimport { registry } from \"@web/core/registry\";\n\nexport class HeaderStandard extends BaseHeader {\n    static selector = \"header.o_header_standard:not(.o_header_sidebar)\";\n\n    setup() {\n        super.setup();\n        this.transitionPoint = 300;\n        this.transitionPossible = false;\n    }\n\n    /**\n     * Checks if the size of the header will decrease by adding the\n     * 'o_header_is_scrolled' class. If so, we do not add this class if the\n     * remaining scroll height is not enough to stay above 'this.transitionPoint'\n     * after the transition, otherwise it causes the scroll position to move up\n     * again below 'this.transitionPoint' and trigger an infinite loop.\n     *\n     * @todo header effects should be improved in the future to not ever change\n     * the page scroll-height during their animation. The code would probably be\n     * simpler but also prevent having weird scroll \"jumps\" during animations\n     * (= depending on the logo height after/before scroll, a scroll step (one\n     * mousewheel event for example) can be bigger than other ones).\n     *\n     * @returns {boolean}\n     */\n    canTransition() {\n        const scrollEl = this.scrollingElement;\n        const remainingScroll =\n            scrollEl.scrollHeight - scrollEl.clientHeight - this.transitionPoint;\n        const clonedHeader = this.el.cloneNode(true);\n        scrollEl.append(clonedHeader);\n        clonedHeader.classList.add(\n            \"o_header_is_scrolled\",\n            \"o_header_affixed\",\n            \"o_header_no_transition\"\n        );\n        const endHeaderHeight = clonedHeader.offsetHeight;\n        clonedHeader.remove();\n        const requiredScroll = this.getHeaderHeight() - endHeaderHeight;\n        return requiredScroll > 0 ? remainingScroll > requiredScroll : true;\n    }\n\n    onScroll() {\n        super.onScroll();\n\n        const scroll = this.scrollingElement.scrollTop;\n\n        const isScrolled = scroll > this.transitionPoint;\n        if (this.isScrolled !== isScrolled) {\n            this.transitionPossible = this.canTransition() || !isScrolled;\n            if (this.transitionPossible) {\n                this.adaptToHeaderChangeLoop(1);\n            }\n        }\n\n        const reachHeaderBottom = scroll > this.getHeaderHeight() + this.topGap;\n        const reachTransitionPoint =\n            scroll > this.transitionPoint + this.topGap && this.transitionPossible;\n\n        if (this.atTop == reachHeaderBottom) {\n            this.el.classList.add(\"o_transformed_not_affixed\");\n        }\n        this.atTop = !reachHeaderBottom;\n\n        reachTransitionPoint\n            ? this.transformShow()\n            : reachHeaderBottom\n            ? this.transformHide()\n            : this.transformShow();\n        void this.el.offsetWidth; // Force a paint refresh\n\n        this.hideEl?.classList.toggle(\"hidden\", reachHeaderBottom);\n\n        this.toggleCSSAffixed(reachHeaderBottom);\n        this.el.classList.remove(\"o_transformed_not_affixed\");\n        this.isScrolled = reachTransitionPoint;\n    }\n\n    getHeaderHeight() {\n        if (this.hideEl) {\n            if (this.isSmall()) {\n                // Ensure we don't consider the hiddenOnScroll element on mobile\n                return this.el.getBoundingClientRect().height;\n            }\n            if (this.hideEl.classList.contains(\"hidden\")) {\n                // Ensure the header height stays the same on desktop\n                return this.hideElHeight + this.el.getBoundingClientRect().height;\n            }\n            this.hideElHeight = this.hideEl?.getBoundingClientRect().height;\n        }\n        return this.el.getBoundingClientRect().height;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.header_standard\", HeaderStandard);\n\nregistry.category(\"public.interactions.edit\").add(\"website.header_standard\", {\n    Interaction: HeaderStandard,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class HeaderTop extends Interaction {\n    static selector = \"header#top\";\n    dynamicContent = {\n        \"#top_menu_collapse, #top_menu_collapse_mobile\": {\n            \"t-on-show.bs.offcanvas\": () => (this.showCollapse = true),\n            \"t-on-hidden.bs.offcanvas\": () =>\n                (this.showCollapse &&= this.mobileNavbarEl.matches(\".show, .showing\")),\n            \"t-att-class\": () => ({\n                o_top_menu_collapse_shown: this.showCollapse,\n            }),\n        },\n    };\n\n    setup() {\n        this.showCollapse = false;\n        this.mobileNavbarEl = this.el.querySelector(\"#top_menu_collapse_mobile\");\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.header_top\", HeaderTop);\n\nregistry.category(\"public.interactions.edit\").add(\"website.header_top\", {\n    Interaction: HeaderTop,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { onceAllImagesLoaded } from \"@website/utils/images\";\n\n/**\n * The websites, by default, use image lazy loading via the loading=\"lazy\"\n * attribute on <img> elements. However, this does not work great on all\n * browsers. This widget fixes the behaviors with as less code as possible.\n */\nexport class ImageLazyLoading extends Interaction {\n    static selector = \"#wrapwrap img[loading='lazy']\";\n\n    setup() {\n        // For each image on the page, force a 1px min-height so that Chrome\n        // understands the image exists on different zoom sizes of the browser.\n        // Indeed, without this, on a 90% zoom, some images were never loaded.\n        // Once the image has been loaded, the 1px min-height is removed.\n        // Note: another possible solution without JS would be this CSS rule:\n        // ```\n        // [loading=\"lazy\"] {\n        //     min-height: 1px;\n        // }\n        // ```\n        // This would solve the problem the same way with a CSS rule with a\n        // very small priority (any class setting a min-height would still have\n        // priority). However, the min-height would always be forced even once\n        // the image is loaded, which could mess with some layouts relying on\n        // the image intrinsic min-height.\n        this.initialHeight = this.el.style.minHeight;\n        this.el.style.minHeight = \"1px\";\n    }\n\n    start() {\n        onceAllImagesLoaded(this.el).then(() => {\n            if (!this.isDestroyed) {\n                this.el.style.minHeight = this.initialHeight;\n            }\n        });\n    }\n\n    destroy() {\n        this.el.style.minHeight = this.initialHeight;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.image_lazy_loading\", ImageLazyLoading);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class ImageShapeHoverEffect extends Interaction {\n    static selector = \"img[data-hover-effect]\";\n    dynamicContent = {\n        _root: {\n            \"t-on-mouseenter\": this.mouseEnter,\n            \"t-on-mouseleave\": this.mouseLeave,\n        },\n    };\n\n    setup() {\n        this.lastMouseEvent = Promise.resolve();\n        this.originalImgSrc = this.el.getAttribute(\"src\");\n        this.svgInEl = null;\n        this.svgOutEl = null;\n        // Observe the src attribute for modifications made outside this\n        // interaction's scope.\n        this.sourceObserver = new MutationObserver(() => {\n            this.originalImgSrc = this.el.src;\n        });\n        this.connectSourceObserver();\n        this.adjustImageSourceFrom = this.protectSyncAfterAsync(this.adjustImageSourceFrom);\n    }\n\n    destroy() {\n        this.el.src = this.originalImgSrc;\n        this.disconnectSourceObserver();\n    }\n    connectSourceObserver() {\n        this.sourceObserver.observe(this.el, {\n            attributes: true,\n            attributeFilter: [\"src\"],\n        });\n    }\n    disconnectSourceObserver() {\n        if (this.sourceObserver) {\n            this.sourceObserver.disconnect();\n        }\n    }\n\n    mouseEnter() {\n        if (!this.originalImgSrc || !this.el.dataset.hoverEffect) {\n            return;\n        }\n        this.lastMouseEvent = this.lastMouseEvent.then(\n            () =>\n                new Promise((resolve) => {\n                    if (!this.svgInEl) {\n                        fetch(this.el.src)\n                            .then((response) => response.text())\n                            .then((text) => {\n                                const parser = new DOMParser();\n                                const result = parser.parseFromString(text, \"text/xml\");\n                                const svg = result.getElementsByTagName(\"svg\")[0];\n                                this.svgInEl = svg;\n                                if (!this.svgInEl) {\n                                    resolve();\n                                    return;\n                                }\n                                // Start animations.\n                                const animateEls = this.svgInEl.querySelectorAll(\n                                    \"#hoverEffects animateTransform, #hoverEffects animate\"\n                                );\n                                animateEls.forEach((animateTransformEl) => {\n                                    animateTransformEl.removeAttribute(\"begin\");\n                                });\n                                this.setImgSrc(this.svgInEl, resolve);\n                            })\n                            .catch(() => {\n                                // Could be the case if somehow the `src` is an absolute\n                                // URL from another domain.\n                            });\n                    } else {\n                        this.setImgSrc(this.svgInEl, resolve);\n                    }\n                })\n        );\n    }\n\n    mouseLeave() {\n        this.lastMouseEvent = this.lastMouseEvent.then(\n            () =>\n                new Promise((resolve) => {\n                    if (!this.originalImgSrc || !this.svgInEl || !this.el.dataset.hoverEffect) {\n                        resolve();\n                        return;\n                    }\n                    if (!this.svgOutEl) {\n                        // Reverse animations.\n                        this.svgOutEl = this.svgInEl.cloneNode(true);\n                        const animateTransformEls = this.svgOutEl.querySelectorAll(\n                            \"#hoverEffects animateTransform, #hoverEffects animate\"\n                        );\n                        animateTransformEls.forEach((animateTransformEl) => {\n                            let valuesValue = animateTransformEl.getAttribute(\"values\");\n                            valuesValue = valuesValue.split(\";\").reverse().join(\";\");\n                            animateTransformEl.setAttribute(\"values\", valuesValue);\n                        });\n                    }\n                    this.setImgSrc(this.svgOutEl, resolve);\n                })\n        );\n    }\n\n    /**\n     * Converts the SVG to a data URI and set it as the image source.\n     *\n     * @param {HTMLElement} svg\n     * @param {Function} resolve\n\ufffc    */\n    setImgSrc(svg, resolve) {\n        if (this.isDestroyed) {\n            return;\n        }\n        // Add random class to prevent browser from caching image. Otherwise the\n        // animations do not trigger more than once.\n        const previousRandomClass = [...svg.classList].find((cl) =>\n            cl.startsWith(\"o_shape_anim_random_\")\n        );\n        svg.classList.remove(previousRandomClass);\n        svg.classList.add(\"o_shape_anim_random_\" + Date.now());\n        // Convert the SVG element to a data URI.\n        const svg64 = btoa(new XMLSerializer().serializeToString(svg));\n        // The image is preloaded to avoid a flickering when it is added to the\n        // DOM.\n        const preloadedImg = new Image();\n        preloadedImg.src = `data:image/svg+xml;base64,${svg64}`;\n        preloadedImg.onload = () => {\n            if (this.isDestroyed) {\n                // In some cases, it is possible for the \"preloadedImg\" to\n                // finish loading while the widget has already been destroyed.\n                // So, we do not set the image source because that can cause\n                // unexpected reverse of the animation.\n                resolve();\n                return;\n            }\n            this.adjustImageSourceFrom(preloadedImg);\n            this.hoveringImgSrc = preloadedImg.getAttribute(\"src\");\n            this.el.onload = () => {\n                resolve();\n            };\n        };\n    }\n\n    /**\n     * Overridable method called once the preloadedImageEl is loaded in\n     * setImgSrc.\n     *\n     * @param {HTMLImageElement} preloadedImageEl\n     */\n    adjustImageSourceFrom(preloadedImageEl) {\n        if (this.isDestroyed) {\n            return;\n        }\n        this.disconnectSourceObserver();\n        this.el.src = preloadedImageEl.getAttribute(\"src\");\n        this.connectSourceObserver();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.image_shape_hover_effect\", ImageShapeHoverEffect);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { rpc } from \"@web/core/network/rpc\";\n\nexport class ListingLayout extends Interaction {\n    static selector = \".o_website_listing_layout\";\n    dynamicContent = {\n        \".listing_layout_switcher input\": {\n            \"t-on-change\": this.onApplyLayoutChange,\n        },\n        \".o_website_grid, .o_website_list\": {\n            \"t-att-class\": () => ({\n                o_website_list: this.isList,\n                o_website_grid: !this.isList,\n            }),\n        },\n        \".o_website_grid > div, .o_website_list > div\": {\n            \"t-att-class\": () => ({\n                \"col-lg-3 col-md-4 col-sm-6 px-2 col-xs-12\": !this.isList,\n            }),\n        },\n    };\n\n    setup() {\n        this.isList = this.el.querySelector(\".o_website_list\") != null;\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    async onApplyLayoutChange(ev) {\n        const clickedValue = ev.target.value;\n        this.isList = clickedValue === \"list\";\n        await this.waitFor(\n            rpc(\"/website/save_session_layout_mode\", {\n                layout_mode: this.isList ? \"list\" : \"grid\",\n                view_id: document.querySelector(\".listing_layout_switcher\").dataset.viewId,\n            })\n        );\n\n        const activeClasses = ev.target.parentElement.dataset.activeClasses.split(\" \");\n        ev.target.parentElement.querySelectorAll(\".btn\").forEach((btn) => {\n            activeClasses.map((c) => btn.classList.toggle(c));\n        });\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.listing_layout\", ListingLayout);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class Parallax extends Interaction {\n    static selector = \".parallax\";\n    dynamicSelectors = Object.assign(this.dynamicSelectors, {\n        _modal: () => this.el.closest(\".modal\"),\n        _bg: () => this.el.querySelector(\":scope > .s_parallax_bg\"),\n    });\n    dynamicContent = {\n        _document: { \"t-on-scroll\": this.onScroll },\n        _window: { \"t-on-resize\": this.updateBackgroundHeight },\n        _modal: { \"t-on-shown.bs.modal\": this.updateBackgroundHeight },\n        _bg: {\n            \"t-att-style\": () => ({\n                top: this.styleTop,\n                bottom: this.styleBottom,\n                transform: this.styleTransform,\n            }),\n        },\n    };\n\n    setup() {\n        this.speed = 0;\n        this.ratio = 0;\n        this.viewportHeight = 0;\n        this.parallaxHeight = 0;\n        this.minScrollPos = 0;\n        this.maxScrollPos = 0;\n\n        this.styleTop = undefined;\n        this.styleBottom = undefined;\n        this.styleTransform = undefined;\n        this.isZoomIn = undefined;\n        this.isZoomOut = undefined;\n    }\n\n    start() {\n        this.updateBackgroundHeight();\n        this.updateContent();\n    }\n\n    updateBackgroundHeight() {\n        this.speed = parseFloat(this.el.getAttribute(\"data-scroll-background-ratio\")) || 0;\n        if (this.speed === 0 || this.speed === 1) {\n            return;\n        }\n        this.viewportHeight = document.body.clientHeight;\n        this.parallaxHeight = this.el.getBoundingClientRect().height;\n\n        // The parallax is in the viewport if it is between these two values\n        // min : bottom of the parallax in at the top of the page\n        // max : top of the parallax in at the bottom of the page\n        this.minScrollPos = -this.parallaxHeight;\n        this.maxScrollPos = this.viewportHeight;\n\n        this.ratio = this.speed * (this.viewportHeight / 10);\n\n        this.styleTop = -Math.abs(this.ratio) + \"px\";\n        this.styleBottom = -Math.abs(this.ratio) + \"px\";\n\n        const parallaxType = this.el.dataset.parallaxType;\n        // Compatibility: Previously, \"zoom_out\" and \"zoom_in\" had their\n        // behavior reversed. The previous \"zoom_out\" correspond to the\n        // current \"zoomIn\" type.\n        this.isZoomIn = parallaxType === \"zoomIn\" || parallaxType === \"zoom_out\";\n        this.isZoomOut = parallaxType === \"zoomOut\" || parallaxType === \"zoom_in\";\n\n        this.onScroll();\n    }\n\n    onScroll() {\n        const currentPosition = this.el.getBoundingClientRect().top;\n        if (\n            this.speed === 0 ||\n            this.speed === 1 ||\n            currentPosition < this.minScrollPos ||\n            currentPosition > this.maxScrollPos\n        ) {\n            return;\n        }\n        // Calculate progress based on the element's visible range\n        const scrollRange = this.maxScrollPos - this.minScrollPos;\n        const progress = Math.min(\n            1,\n            Math.max(0, (currentPosition - this.minScrollPos) / scrollRange)\n        );\n\n        if (this.isZoomOut) {\n            const initialZoom = 1;\n            const maxZoom = this.speed + 1;\n\n            this.styleTransform = `scale(${initialZoom + (maxZoom - initialZoom) * progress})`;\n        } else if (this.isZoomIn) {\n            const initialZoom = this.speed + 1;\n\n            this.styleTransform = `scale(${initialZoom - (initialZoom - 1) * progress})`;\n        } else {\n            const r = 1 / (this.minScrollPos - this.maxScrollPos);\n            const offset = 1 - 2 * this.minScrollPos * r;\n            const movement = -Math.round(this.ratio * (r * currentPosition + offset));\n\n            this.styleTransform = \"translateY(\" + movement + \"px)\";\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.parallax\", Parallax);\n\nregistry.category(\"public.interactions.edit\").add(\"website.parallax\", {\n    Interaction: Parallax,\n});\n", "import { Parallax } from \"@website/interactions/parallax/parallax\";\nimport { registry } from \"@web/core/registry\";\n\n// A manual parallax implementation is required for snippet previews because\n// snippets are scaled down in preview, and `background-attachment: fixed`\n// (which enables the native parallax effect) does not work on transformed\n// elements.\n//\n// To simulate parallax behavior, we manually adjust the background position\n// relative to the scroll position.\n\nconst ParallaxPreview = (I) =>\n    class extends I {\n        // The PARALLAX_RATE controls how fast the background moves.\n        // A higher PARALLAX_RATE means faster movement, which requires a larger\n        // background SCALE to prevent background cutoff.\n        PARALLAX_RATE = 16;\n        SCALE = 2.4;\n\n        setup() {\n            this.backgroundEl = this.el.querySelector(\".s_parallax_bg\");\n            this.previewContainerEl = this.el.ownerDocument.body;\n        }\n\n        start() {\n            if (!this.backgroundEl || !this.previewContainerEl) {\n                return;\n            }\n\n            this.applyInitialStyles();\n            this.initializeIntersectionObserver();\n        }\n\n        destroy() {\n            if (this.observer) {\n                this.observer.disconnect();\n                this.observer = null;\n            }\n        }\n\n        applyInitialStyles() {\n            Object.assign(this.el.style, {\n                overflow: \"hidden\",\n            });\n\n            Object.assign(this.backgroundEl.style, {\n                width: \"100%\",\n                height: \"100%\",\n                left: \"-50%\",\n                top: \"-50%\",\n                backgroundPosition: \"center bottom\",\n                transform: `translate(50%, 50%) scale(${this.SCALE})`,\n                willChange: \"transform\",\n            });\n        }\n\n        /**\n         * Sets up an IntersectionObserver to detect when the element enters\n         * or leaves the viewport, ensuring the parallax effect is applied\n         * only when necessary.\n         */\n        initializeIntersectionObserver() {\n            this.observer = new IntersectionObserver((entries) => {\n                entries.forEach((entry) => {\n                    if (entry.isIntersecting) {\n                        this.updateParallaxPosition();\n                        this.previewContainerEl.addEventListener(\n                            \"scroll\",\n                            this.updateParallaxPosition\n                        );\n                    } else {\n                        this.previewContainerEl.removeEventListener(\n                            \"scroll\",\n                            this.updateParallaxPosition\n                        );\n                    }\n                });\n            });\n\n            this.observer.observe(this.el);\n        }\n\n        /**\n         * Updates the background position to create a parallax effect based on\n         * scroll position.\n         */\n        updateParallaxPosition = () => {\n            const rect = this.el.getBoundingClientRect();\n            const relativeScrollProgress = rect.top / this.previewContainerEl.clientHeight;\n\n            const parallaxShift = relativeScrollProgress * this.PARALLAX_RATE * 100;\n            this.backgroundEl.style.transform = `translate(50%, calc(50% - ${parallaxShift}px)) scale(${this.SCALE})`;\n        };\n    };\n\nregistry.category(\"public.interactions.preview\").add(\"website.parallax\", {\n    Interaction: Parallax,\n    mixin: ParallaxPreview,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class PlausiblePush extends Interaction {\n    static selector = \".js_plausible_push\";\n\n    setup() {\n        const { eventName, eventParams } = this.el.dataset;\n\n        window.plausible ||= function () {\n            (window.plausible.q = window.plausible.q || []).push(arguments);\n        };\n        window.plausible(eventName, { props: JSON.parse(eventParams) || {} });\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.plausible_push\", PlausiblePush);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { isScrollableY } from \"@web/core/utils/scrolling\";\n\nexport class NoBackdropPopup extends Interaction {\n    static selector = \".s_popup_no_backdrop\";\n    dynamicContent = {\n        _root: {\n            \"t-on-shown.bs.modal\": this.addModalNoBackdropEvents,\n            \"t-on-hide.bs.modal\": this.removeModalNoBackdropEvents,\n        },\n    };\n\n    setup() {\n        this.throttledUpdateScrollbar = this.throttled(this.updateScrollbar);\n        this.removeResizeListener = null;\n        this.resizeObserver = null;\n    }\n\n    destroy() {\n        this.removeModalNoBackdropEvents();\n        // After destroying the interaction, we need to trigger a resize event\n        // so that the scrollbar can adjust to its default behavior.\n        window.dispatchEvent(new Event(\"resize\"));\n    }\n\n    updateScrollbar() {\n        // When there is no backdrop the element with the scrollbar is\n        // '.modal-content' (see comments in CSS).\n        const modalContentEl = this.el.querySelector(\".modal-content\");\n        const isOverflowing = isScrollableY(modalContentEl);\n        const bsModal = window.Modal.getOrCreateInstance(this.el);\n        if (isOverflowing) {\n            // If the \"no-backdrop\" modal has a scrollbar, the page's scrollbar\n            // must be hidden. This is because if the two scrollbars overlap, it\n            // is no longer possible to scroll using the modal's scrollbar.\n            bsModal._adjustDialog();\n        } else {\n            // If the \"no-backdrop\" modal does not have a scrollbar, the page\n            // scrollbar must be displayed because we must be able to scroll the\n            // page (e.g. a \"cookies bar\" popup at the bottom of the page must\n            // not prevent scrolling the page).\n            bsModal._resetAdjustments();\n        }\n    }\n\n    addModalNoBackdropEvents() {\n        this.updateScrollbar();\n        this.removeResizeListener = this.addListener(\n            window,\n            \"resize\",\n            this.throttledUpdateScrollbar\n        );\n        this.resizeObserver = new window.ResizeObserver(() => {\n            // When the size of the modal changes, the scrollbar needs to be\n            // adjusted.\n            this.updateScrollbar();\n        });\n        this.resizeObserver.observe(this.el.querySelector(\".modal-content\"));\n    }\n\n    removeModalNoBackdropEvents() {\n        this.throttledUpdateScrollbar.cancel();\n        if (this.resizeObserver) {\n            this.removeResizeListener();\n            this.resizeObserver.disconnect();\n            delete this.resizeObserver;\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.no_backdrop_popup\", NoBackdropPopup);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { utils as uiUtils, SIZES } from \"@web/core/ui/ui_service\";\nimport { getTabableElements } from \"@web/core/utils/ui\";\n\nexport class Popup extends Interaction {\n    static selector = \".s_popup:not(#website_cookies_bar)\";\n    dynamicContent = {\n        \".js_close_popup\": {\n            \"t-on-click\": this.onCloseClick,\n        },\n        \".btn-primary\": {\n            \"t-on-click\": this.onBtnPrimaryClick,\n        },\n        _root: {\n            \"t-on-hide.bs.modal\": this.onHideModal,\n            \"t-on-shown.bs.modal\": this.trapFocus,\n        },\n        _window: {\n            \"t-on-hashchange\": this.onHashChange,\n        },\n        \".modal:not(.s_popup_no_backdrop)\": {\n            // Here, bootstrap's data-bs-backdrop attribute is not used and\n            // we use a custom click handler instead to dismiss the popup on\n            // click outside as we do not use bootstrap native backdrop.\n            // See MODAL_BACKDROP_WEBSITE.\n            \"t-on-click\": this.onBackdropModalClick,\n        },\n    };\n\n    setup() {\n        this.cookieValue = true;\n        this.modalEl = this.el.querySelector(\".modal\");\n        /** @type {import(\"bootstrap\").Modal} */\n        this.bsModal = window.Modal.getOrCreateInstance(this.modalEl);\n        this.registerCleanup(() => {\n            this.bsModal.dispose();\n        });\n\n        this.modalShownOnClickEl = this.el.querySelector(\".modal[data-display='onClick']\");\n        if (this.modalShownOnClickEl) {\n            this.showModalBtnEl = document.querySelector(\n                `[href=\"#${this.modalShownOnClickEl.id}\"]`\n            );\n            // Check if a hash exists and if the modal needs to be opened when\n            // the page loads (e.g. The user has clicked a button on the\n            // \"Contact us\" page to open a popup on the homepage).\n            this.showPopupOnClick();\n            return;\n        }\n\n        this.popupAlreadyShown = !!cookie.get(this.el.id);\n    }\n\n    start() {\n        // Check if every child element of the popup is conditionally hidden,\n        // and if so, never show an empty popup.\n        // config.device.isMobile is true if the device is <= SM, but the device\n        // visibility option uses < LG to hide on mobile. So compute it here.\n        const isMobile = uiUtils.getSize() < SIZES.LG;\n        const emptyPopup = [\n            ...this.el.querySelectorAll(\".oe_structure > *:not(.s_popup_close)\"),\n        ].every((el) => {\n            const visibilitySelectors = el.dataset.visibilitySelectors;\n            const deviceInvisible = isMobile\n                ? el.classList.contains(\"o_snippet_mobile_invisible\")\n                : el.classList.contains(\"o_snippet_desktop_invisible\");\n            return (visibilitySelectors && el.matches(visibilitySelectors)) || deviceInvisible;\n        });\n        if (!this.popupAlreadyShown && !emptyPopup) {\n            this.bindPopup();\n        }\n    }\n\n    bindPopup() {\n        let display = this.modalEl.dataset.display;\n        let delay = parseInt(this.modalEl.dataset.showAfter);\n\n        if (uiUtils.isSmall()) {\n            if (display === \"mouseExit\") {\n                display = \"afterDelay\";\n                delay = 5000;\n            }\n        }\n\n        if (display === \"afterDelay\") {\n            this.waitForTimeout(this.showPopup, delay);\n        } else if (display === \"mouseExit\") {\n            this.addListener(document.body, \"mouseleave\", this.showPopup);\n        }\n    }\n\n    canShowPopup() {\n        return true;\n    }\n\n    hidePopup() {\n        this.bsModal.hide();\n    }\n\n    showPopup() {\n        if (this.popupAlreadyShown || !this.canShowPopup()) {\n            return;\n        }\n        this.bsModal.show();\n        this.registerCleanup(() => {\n            // Do not call .hide() directly, because it is queued whereas\n            // .dispose() is not, making it crash. As we don't have to wait for\n            // animations here, bypass the issue with ._hideModal().\n            // Additionally, .hide() triggers `hide.bs.modal`, which triggers\n            // onHideModal() and sets a cookie: we don't want that on destroy.\n            this.modalEl.classList.remove(\"show\");\n            this.bsModal._hideModal();\n        });\n    }\n\n    /**\n     * @param {String} [hash]\n     */\n    showPopupOnClick(hash = browser.location.hash) {\n        // If a hash exists in the URL and it corresponds to the ID of the modal,\n        // then we open the modal.\n        if (hash && hash.substring(1) === this.modalShownOnClickEl.id) {\n            // We remove the hash from the URL because otherwise the popup\n            // cannot open again after being closed.\n            const urlWithoutHash = browser.location.href.replace(hash, \"\");\n            browser.history.replaceState(null, null, urlWithoutHash);\n            this.showPopup();\n        }\n    }\n\n    /**\n     * Checks if the given primary button should allow or not to close the\n     * modal.\n     *\n     * @param {HTMLElement} primaryBtnEl\n     */\n    canBtnPrimaryClosePopup(primaryBtnEl) {\n        return !(\n            primaryBtnEl.classList.contains(\"s_website_form_send\") ||\n            primaryBtnEl.classList.contains(\"o_website_form_send\")\n        );\n    }\n\n    /**\n     * Traps the focus within the modal.\n     *\n     * @returns {Function} refocuses the element that was focused before the\n     * modal opened.\n     */\n    trapFocus() {\n        let tabableEls = getTabableElements(this.el);\n        let previouslyFocusedEl;\n        if (this.showModalBtnEl) {\n            previouslyFocusedEl = this.showModalBtnEl;\n        } else {\n            previouslyFocusedEl = document.activeElement || document.body;\n        }\n        if (tabableEls.length) {\n            tabableEls[0].focus();\n            this.el.querySelector(\".modal\").scrollTop = 0;\n        } else {\n            this.el.focus();\n        }\n        // The focus should stay free for no backdrop popups.\n        if (this.el.querySelector(\".s_popup_no_backdrop\")) {\n            this.addListener(this.el, \"hide.bs.modal\", () => previouslyFocusedEl.focus(), {\n                once: true,\n            });\n            return;\n        }\n        const onKeydown = (ev) => {\n            if (ev.key !== \"Tab\") {\n                return;\n            }\n            // Update tabableEls: they might have changed in the meantime.\n            tabableEls = getTabableElements(this.el);\n            if (!tabableEls.length) {\n                ev.preventDefault();\n                return;\n            }\n            if (!ev.shiftKey && ev.target === tabableEls[tabableEls.length - 1]) {\n                ev.preventDefault();\n                tabableEls[0].focus();\n            }\n            if (ev.shiftKey && ev.target === tabableEls[0]) {\n                ev.preventDefault();\n                tabableEls[tabableEls.length - 1].focus();\n            }\n        };\n        const removeOnKeydown = this.addListener(this.el, \"keydown\", onKeydown);\n        this.addListener(\n            this.el,\n            \"hide.bs.modal\",\n            () => {\n                removeOnKeydown();\n                previouslyFocusedEl.focus();\n            },\n            { once: true }\n        );\n    }\n\n    onCloseClick() {\n        this.hidePopup();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onBtnPrimaryClick(ev) {\n        if (this.canBtnPrimaryClosePopup(ev.target)) {\n            this.hidePopup();\n        }\n    }\n\n    onHideModal() {\n        const nbDays = this.modalEl.dataset.consentsDuration;\n        cookie.set(this.el.id, this.cookieValue, nbDays * 24 * 60 * 60, \"required\");\n        this.popupAlreadyShown = !this.modalShownOnClickEl;\n    }\n\n    /**\n     * @param {HashChangeEvent} ev\n     */\n    onHashChange(ev) {\n        if (this.modalShownOnClickEl) {\n            // Keep the new hash from the event to avoid conflict with the eCommerce\n            // hash attributes managing.\n            // TODO : it should not have been a hash at all for ecommerce, but a\n            // query string parameter\n            this.showPopupOnClick(new URL(ev.newURL).hash);\n        }\n    }\n\n    /**\n     * Handles clicks outside the popup to dismiss it.\n     *\n     * @param {MouseEvent} ev\n     */\n    onBackdropModalClick(ev) {\n        if (ev.target === ev.currentTarget) {\n            this.hidePopup();\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.popup\", Popup);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class SharedPopup extends Interaction {\n    static selector = \".s_popup\";\n    dynamicContent = {\n        // A popup element is composed of a `.s_popup` parent containing the\n        // actual `.modal` BS modal. Our internal logic and events are hiding\n        // and showing this inner `.modal` modal element without considering its\n        // `.s_popup` parent. It means that when the `.modal` is hidden, its\n        // `.s_popup` parent is not touched and kept visible.\n        // It might look like it's not an issue as it would just be an empty\n        // element (its only child is hidden) but it leads to some issues as for\n        // instance on chrome this div will have a forced `height` due to its\n        // `contenteditable=true` attribute in edit mode. It will result in a\n        // ugly white bar.\n        // tl;dr: this is keeping those 2 elements visibility synchronized.\n        _root: {\n            \"t-on-show.bs.modal.noUpdate\": () => {\n                this.popupShown = true;\n                // Combining noUpdate and `this.updateContent()` forces a\n                // repaint immediately to remove `.d-none` before the transition\n                // happens. Otherwise, the transition isn't visible.\n                this.updateContent();\n            },\n            \"t-on-shown.bs.modal\": () => (this.popupShown = true),\n            \"t-on-hidden.bs.modal\": this.onModalHidden,\n            \"t-att-class\": () => ({ \"d-none\": !this.popupShown }),\n        },\n    };\n\n    setup() {\n        this.popupShown = false;\n    }\n\n    onModalHidden() {\n        if (this.el.querySelector(\".s_popup_no_backdrop\")) {\n            // We trigger a scroll event here to call the\n            // '_hideBottomFixedElements' method and re-display any bottom fixed\n            // elements that may have been hidden (e.g. the live chat button\n            // hidden when the cookies bar is open).\n            window.dispatchEvent(new Event(\"scroll\"));\n        }\n        this.popupShown = false;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.shared_popup\", SharedPopup);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { sendRequest } from \"@website/js/utils\";\n\nexport class PostLink extends Interaction {\n    static selector = \".post_link\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        // Distinguish _root according to node type.\n        _select: () => this.el.matches(\"select\") && this.el,\n        _nonSelect: () => !this.el.matches(\"select\") && this.el,\n    };\n    dynamicContent = {\n        _root: {\n            \"t-att-class\": () => ({\n                o_post_link_js_loaded: true,\n            }),\n        },\n        _nonSelect: {\n            \"t-on-click.prevent\": this.onClickPost,\n        },\n        _select: {\n            // In some browsers the click event is triggered when opening the select.\n            \"t-on-change.prevent\": this.onClickPost,\n        },\n    };\n\n    onClickPost() {\n        const data = {};\n        for (const [key, value] of Object.entries(this.el.dataset)) {\n            if (key.startsWith(\"post_\")) {\n                data[key.slice(5)] = value;\n            }\n        }\n        sendRequest(this.el.dataset.post || this.el.href || this.el.value, data);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.post_link\", PostLink);\n", "import { AnchorSlide } from \"@website/interactions/anchor_slide\";\nimport { registry } from \"@web/core/registry\";\n\nimport { isVisible } from \"@web/core/utils/ui\";\n\nexport class ScrollButton extends AnchorSlide {\n    static selector = \".o_scroll_button\";\n\n    animateClick() {\n        // Scroll to the next visible element after the current one.\n        const currentSectionEl = this.el.closest(\"section\");\n        let nextEl = currentSectionEl.nextElementSibling;\n        while (nextEl) {\n            if (isVisible(nextEl)) {\n                this.scrollTo(nextEl);\n                return;\n            }\n            nextEl = nextEl.nextElementSibling;\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.scroll_button\", ScrollButton);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class SearchModal extends Interaction {\n    static selector = \"#o_search_modal_block #o_search_modal\";\n    dynamicContent = {\n        _root: {\n            \"t-on-shown.bs.modal\": () => this.el.querySelector(\".search-query\").focus(),\n        },\n    };\n}\n\nregistry.category(\"public.interactions\").add(\"website.search_modal\", SearchModal);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport {\n    getCurrentTextHighlight,\n    makeHighlightSvgs,\n    closestToObserve,\n    getObservedEls,\n} from \"@website/js/highlight_utils\";\n\nexport class TextHighlight extends Interaction {\n    static selector = \"#wrapwrap, .o_wslides_fs_content\";\n    dynamicContent = {\n        _root: {\n            \"t-on-text_highlight_added\": ({ target }) => this.onTextHighlightAdded(target),\n        },\n    };\n\n    setup() {\n        this.observerLock = new Map();\n        this.observed = new WeakSet();\n        this.resizeObserver = new window.ResizeObserver(this.updateEntries.bind(this));\n        this.mutationObserver = new window.MutationObserver(this.updateEntries.bind(this));\n    }\n\n    start() {\n        for (const textEl of this.el.querySelectorAll(\".o_text_highlight\")) {\n            this.handleEl(textEl);\n        }\n    }\n\n    destroy() {\n        this.resizeObserver.disconnect();\n        this.mutationObserver.disconnect();\n    }\n\n    updateEntries(entries) {\n        this.waitForAnimationFrame(() => this._updateEntries(entries));\n    }\n    _updateEntries(entries) {\n        const closestToObserves = new Set();\n        for (const { target, addedNodes = [], removedNodes = [] } of entries) {\n            const elements = [target, ...addedNodes, ...removedNodes]\n                .map((el) => (el.nodeType === Node.ELEMENT_NODE ? el : el.parentElement))\n                .filter(Boolean);\n            if (!elements.length) {\n                continue;\n            }\n            const hasSvg = elements.some((el) => el.closest(\".o_text_highlight_svg\"));\n            if (hasSvg) {\n                continue;\n            }\n            closestToObserves.add(this.closestToObserve(target));\n        }\n        for (const closestToObserve of closestToObserves) {\n            for (const el of closestToObserve.querySelectorAll(\".o_text_highlight\")) {\n                const highlightID = getCurrentTextHighlight(el);\n                const svgs = makeHighlightSvgs(el, highlightID);\n                const currentSVGs = el.querySelectorAll(\".o_text_highlight_svg\");\n                for (const svg of currentSVGs) {\n                    svg.remove();\n                }\n                for (const svg of svgs) {\n                    this.insert(svg, el);\n                }\n            }\n        }\n    }\n    /**\n     * TODO: Remove in master (left in stable for compatibility)\n     *\n     * @param {HTMLElement} el\n     */\n    closestToObserve(el) {\n        return closestToObserve(el, this.el);\n    }\n\n    /**\n     * TODO: Remove in master (left in stable for compatibility)\n     *\n     * @param {HTMLElement} el\n     */\n    getObservedEls(el) {\n        return getObservedEls(el);\n    }\n\n    /**\n     * @param {HTMLElement} el\n     */\n    handleEl(el) {\n        this.observed.add(el);\n        // The `ResizeObserver` cannot detect the width change on highlight\n        // units (`.o_text_highlight_item`) as long as the width of the entire\n        // `.o_text_highlight` element remains the same, so we need to observe\n        // each one of them and do the adjustment only once for the whole text.\n        for (const elToObserve of this.getObservedEls(el)) {\n            this.resizeObserver.observe(elToObserve);\n        }\n        const closestToObserve = this.closestToObserve(el);\n        this.mutationObserver.observe(closestToObserve, {\n            childList: true,\n            characterData: true,\n            subtree: true,\n        });\n        this.mutationObserver.observe(el, {\n            attributes: true,\n        });\n        this.updateEntries([{ target: el }]);\n    }\n\n    /**\n     * @param {HTMLElement} el\n     */\n    onTextHighlightAdded(el) {\n        // todo: what was the purpose of this?\n        // this.lockTextHighlightObserver(el);\n        this.handleEl(el);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.text_highlight\", TextHighlight);\n\nregistry.category(\"public.interactions.edit\").add(\"website.text_highlight\", {\n    Interaction: TextHighlight,\n});\n", "import { TextHighlight } from \"@website/interactions/text_highlights\";\nimport { registry } from \"@web/core/registry\";\n\nconst TextHighlightPreview = (I) =>\n    class extends I {\n        static selector = \".o_snippet_preview_wrap\";\n        static selectorHas = \".o_text_highlight\";\n    };\n\nregistry.category(\"public.interactions.preview\").add(\"website.text_highlight\", {\n    Interaction: TextHighlight,\n    mixin: TextHighlightPreview,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { uniqueId } from \"@web/core/utils/functions\";\nimport { setupAutoplay, triggerAutoplay } from \"@website/utils/videos\";\n\nexport class BackgroundVideo extends Interaction {\n    static selector = \".o_background_video\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _dropdown: () => this.el.closest(\".dropdown-menu\")?.parentElement,\n        _modal: () => this.el.closest(\"modal\"),\n    };\n    dynamicContent = {\n        _document: {\n            // We don't add the optional cookies warning for background videos\n            // so that the fallback message doesn't appear behind the content.\n            \"t-on-optionalCookiesAccepted.once\": () => (this.iframeEl.src = this.videoSrc),\n        },\n        _window: {\n            \"t-on-resize\": this.throttled(this.adjustIframe),\n        },\n        _dropdown: {\n            \"t-on-shown.bs.dropdown\": this.throttled(this.adjustIframe),\n        },\n        _modal: {\n            \"t-on-show.bs.modal\": () => (this.hideVideoContainer = true),\n            \"t-on-shown.bs.modal\": () => (this.hideVideoContainer = false),\n        },\n        \".o_bg_video_container\": {\n            \"t-att-class\": () => ({\n                \"d-none\": this.hideVideoContainer,\n            }),\n        },\n    };\n\n    setup() {\n        this.hideVideoContainer = false;\n        this.videoSrc = this.el.dataset.bgVideoSrc;\n        this.iframeID = uniqueId(\"o_bg_video_iframe_\");\n        this.iframeEl = null;\n        this.bgVideoContainer = null;\n    }\n\n    start() {\n        const promise = setupAutoplay(this.videoSrc, !!this.el.dataset.needCookiesApproval);\n        if (promise) {\n            this.videoSrc += \"&enablejsapi=1\";\n            this.waitFor(promise).then(this.protectSyncAfterAsync(this.appendBgVideo));\n        }\n        this.__adjustIframe = this.throttled(this.adjustIframe);\n        const resizeObserver = new ResizeObserver(this.__adjustIframe.bind(this));\n        // A change in an element padding does not trigger the resizeObserver so\n        // both inner and outer element are observed for any resizing.\n        resizeObserver.observe(this.el.parentElement);\n        resizeObserver.observe(this.el);\n    }\n\n    adjustIframe() {\n        if (!this.iframeEl) {\n            return;\n        }\n\n        this.iframeEl.classList.remove(\"show\");\n\n        const wrapperWidth = this.el.clientWidth;\n        const wrapperHeight = this.el.clientHeight;\n        const relativeRatio = wrapperWidth / wrapperHeight / (16 / 9);\n\n        if (this.el.closest(\".s_ecomm_categories_showcase_block\")) {\n            // Chrome-only: percentage sizing makes the video in \"Categories\n            // Showcase\" snippet jitter on hover, so force pixel values while\n            // keeping the ratio.\n            const iframeHeight = Math.round(\n                relativeRatio >= 1 ? wrapperWidth * (9 / 16) : wrapperHeight\n            );\n            const iframeWidth = Math.round(\n                relativeRatio >= 1 ? wrapperWidth : wrapperHeight * (16 / 9)\n            );\n            this.iframeEl.style.height = `${iframeHeight}px`;\n            this.iframeEl.style.width = `${iframeWidth}px`;\n        } else if (relativeRatio >= 1.0) {\n            this.iframeEl.style.width = \"100%\";\n            this.iframeEl.style.height = relativeRatio * 100 + \"%\";\n            this.iframeEl.style.insetInlineStart = \"0\";\n            this.iframeEl.style.insetBlockStart = (-(relativeRatio - 1.0) / 2) * 100 + \"%\";\n        } else {\n            this.iframeEl.style.width = (1 / relativeRatio) * 100 + \"%\";\n            this.iframeEl.style.height = \"100%\";\n            this.iframeEl.style.insetInlineStart = (-(1 / relativeRatio - 1.0) / 2) * 100 + \"%\";\n            this.iframeEl.style.insetBlockStart = \"0\";\n        }\n\n        void this.iframeEl.offsetWidth; // Force style addition\n        this.iframeEl.classList.add(\"show\");\n    }\n\n    appendBgVideo() {\n        const allowedCookies = !this.el.dataset.needCookiesApproval;\n\n        const oldContainer =\n            this.bgVideoContainer || this.el.querySelector(\":scope > .o_bg_video_container\");\n        oldContainer?.remove();\n\n        this.renderAt(\n            \"website.background.video\",\n            {\n                videoSrc: allowedCookies ? this.videoSrc : \"about:blank\",\n                iframeID: this.iframeID,\n            },\n            this.el,\n            \"afterbegin\"\n        );\n\n        this.bgVideoContainer = this.el.querySelector(\":scope > .o_bg_video_container\");\n        this.iframeEl = this.bgVideoContainer.querySelector(\".o_bg_video_iframe\");\n        this.addListener(\n            this.iframeEl,\n            \"load\",\n            () => {\n                this.bgVideoContainer.querySelector(\".o_bg_video_loading\")?.remove();\n                // When there is a \"slide in (left or right) animation\" element,\n                // we need to adjust the iframe size once it has been loaded,\n                // otherwise an horizontal scrollbar may appear.\n                this.adjustIframe();\n            },\n            { once: true }\n        );\n\n        this.adjustIframe();\n        triggerAutoplay(this.iframeEl);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.background_video\", BackgroundVideo);\n\nregistry.category(\"public.interactions.edit\").add(\"website.background_video\", {\n    Interaction: BackgroundVideo,\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { setupAutoplay, triggerAutoplay } from \"@website/utils/videos\";\nimport { generateVideoIframe } from \"@website/js/content/generate_video_iframe\";\n\nexport class MediaVideo extends Interaction {\n    static selector = \".media_iframe_video\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _popup: () => this.el.closest(\".s_popup\"),\n    };\n    dynamicContent = {\n        _popup: {\n            \"t-on-shown.bs.modal\": () => {\n                // TODO still oeExpression to remove someday\n                this.services.website_cookies.manageIframeSrc(\n                    this.el.querySelector(\"iframe\"),\n                    this.el.dataset.oeExpression || this.el.dataset.src\n                );\n            },\n            \"t-on-hide.bs.modal\": () => {\n                this.el.querySelector(\"iframe\").src = \"\";\n            },\n        },\n        _document: {\n            \"t-on-optionalCookiesAccepted\": () => {\n                this.cookiesAccepted = true;\n            },\n        },\n        \":scope > .media_iframe_video_size\": {\n            \"t-att-class\": () => ({ \"d-none\": !this.cookiesAccepted }),\n        },\n    };\n\n    setup() {\n        this.cookiesAccepted = this.el.dataset.needCookiesApproval !== \"true\";\n    }\n\n    start() {\n        let iframeEl = this.el.querySelector(\":scope > iframe\");\n\n        // Generate the video `<iframe/>` element when restarting interacions.\n        // In some cases (e.g., when adding a new video block), we don\u2019t need\n        // to rebuild the same iframe while starting the widget.\n        if (!iframeEl) {\n            iframeEl = generateVideoIframe(this.el, this.services.website_cookies.manageIframeSrc);\n        }\n\n        if (iframeEl && !iframeEl.getAttribute(\"aria-label\")) {\n            iframeEl.setAttribute(\"aria-label\", _t(\"Media video\"));\n        }\n\n        if (iframeEl?.hasAttribute(\"src\")) {\n            const promise = setupAutoplay(\n                iframeEl.getAttribute(\"src\"),\n                !!this.el.dataset.needCookiesApproval\n            );\n            if (promise) {\n                this.waitFor(promise).then(\n                    this.protectSyncAfterAsync(() => triggerAutoplay(iframeEl))\n                );\n            }\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.media_video\", MediaVideo);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * @todo while this solution mitigates the issue, it is not fixing it entirely\n * but mainly, we should find a better solution than a JS solution as soon as\n * one is available and ideally without having to make ugly patches to the SVGs.\n *\n * Due to a bug on Chrome when using browser zoom, there is sometimes a gap\n * between sections with shapes. This gap is due to a rounding issue when\n * positioning the SVG background images. This code reduces the rounding error\n * by ensuring that shape elements always have a width value as close to an\n * integer as possible.\n *\n * Note: a gap also appears between some shapes without zoom. This is likely\n * due to error in the shapes themselves. Many things were done to try and fix\n * this, but the remaining errors will likely be fixed with a review of the\n * shapes in future Odoo versions.\n *\n * /!\\\n * If a better solution for stable comes up, this widget behavior may be\n * disabled, avoid depending on it if possible.\n * /!\\\n */\nexport class ZoomedBackgroundShape extends Interaction {\n    static selector = \".o_we_shape\";\n    dynamicContent = {\n        _window: {\n            \"t-on-resize\": this.throttled(this.resizeBackgroundShape),\n        },\n        _root: {\n            \"t-att-style\": () => ({\n                left: this.offset,\n                right: this.offset,\n            }),\n        },\n    };\n\n    setup() {\n        this.offset = undefined;\n    }\n\n    start() {\n        // This cannot be move in the setup because updateContent\n        // is not available yet (interaction not ready)\n        this.resizeBackgroundShape();\n        this.updateContent();\n    }\n\n    resizeBackgroundShape() {\n        this.offset = undefined;\n        this.updateContent();\n        // Get the decimal part of the shape element width.\n        let decimalPart = this.el.getBoundingClientRect().width % 1;\n        // Round to two decimal places.\n        decimalPart = parseFloat(decimalPart.toFixed(2));\n        // If the decimal part was 0.99, it was rounded to 1\n        // In that case we consider there was no decimal part\n        decimalPart = decimalPart == 1 ? 0 : decimalPart;\n        // If there is a decimal part. (e.g. Chrome + browser zoom enabled)\n        if (decimalPart > 0) {\n            // Compensate for the gap by giving an integer width value to the\n            // shape by changing its \"right\" and \"left\" positions.\n            this.offset = `${(decimalPart < 0.5 ? decimalPart : decimalPart - 1) / 2}px`;\n            // This never causes the horizontal scrollbar to appear because it\n            // only appears if the overflow to the right exceeds 0.333px.\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.zoomed_background_shape\", ZoomedBackgroundShape);\n", "import { registry } from \"@web/core/registry\";\nimport { PublicComponentInteraction } from \"@web/public/public_component_interaction\";\n\n// We register an editable interaction here to add support for <owl-component/>\n// in edit mode. The idea is that <owl-components /> in edit mode are rendered,\n// but rendered inactive by setting the pointerEvents key to none. To have\n// active components in edit mode, one has to register it in the public_components.edit\n// registry\nconst PublicComponentInteractionEdit = (I) =>\n    class extends I {\n        get Component() {\n            const name = this.el.getAttribute(\"name\");\n            let C = registry.category(\"public_components.edit\").get(name, false);\n            if (!C) {\n                C = super.Component;\n                // disable <owl-component/> in edit mode\n                this.el.style.pointerEvents = \"none\";\n            }\n            return C;\n        }\n    };\n\nregistry.category(\"public.interactions.edit\").add(\"public_components\", {\n    Interaction: PublicComponentInteraction,\n    mixin: PublicComponentInteractionEdit,\n});\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\n\nconst errorHandlerRegistry = registry.category(\"error_handlers\");\n\nlet isUnloadingPage = false;\nwindow.addEventListener(\"beforeunload\", () => {\n    isUnloadingPage = true;\n    // restore after 10 seconds\n    browser.setTimeout(() => (isUnloadingPage = false), 10000);\n});\n\n/**\n * Handles the errors trigger after the before unload event.\n *\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @returns {boolean}\n */\nfunction beforeUnloadHandler(env, error) {\n    if (isUnloadingPage) {\n        error.event.preventDefault();\n        return true;\n    }\n    return false;\n}\n\nerrorHandlerRegistry.add(\"beforeUnloadHandler\", beforeUnloadHandler, { sequence: 1 });\n", "import { registry } from \"@web/core/registry\";\nimport { EventBus } from \"@website/utils/misc\";\n\nexport const websiteCookiesService = {\n    dependencies: [\"public.interactions\"],\n    start(env, deps) {\n        const bus = new EventBus();\n        const publicInteractions = deps[\"public.interactions\"];\n        /**\n         * Updates the element's iframe according to whether the cookies should\n         * be approved (marked by `_post_processing_att` server-side).\n         *\n         * @param {HTMLIFrameElement} iframeEl\n         * @param {string} src - src to set on the iframe.\n         */\n        function manageIframeSrc(iframeEl, src) {\n            if (!iframeEl.closest(\"[data-need-cookies-approval]\")) {\n                iframeEl.setAttribute(\"src\", src);\n            } else {\n                iframeEl.dataset.nocookieSrc = src;\n                iframeEl.setAttribute(\"src\", \"about:blank\");\n                iframeEl.dataset.needCookiesApproval = \"true\";\n                publicInteractions.startInteractions(iframeEl);\n            }\n        }\n\n        return { bus, manageIframeSrc };\n    },\n};\n\nregistry.category(\"services\").add(\"website_cookies\", websiteCookiesService);\n", "import { loadJS } from \"@web/core/assets\";\nimport { registry } from \"@web/core/registry\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { user } from \"@web/core/user\";\nimport { markup } from \"@odoo/owl\";\n\nexport const websiteMapService = {\n    dependencies: [\"public.interactions\", \"notification\"],\n    start(env, deps) {\n        const publicInteractions = deps[\"public.interactions\"];\n        const notification = deps[\"notification\"];\n        let gmapAPIKeyProm;\n        let gmapAPILoading;\n        const promiseKeys = {};\n        const promiseKeysResolves = {};\n        let lastKey;\n        window.odoo_gmap_api_post_load = async function odoo_gmap_api_post_load() {\n            for (const el of document.querySelectorAll(\"section.s_google_map\")) {\n                publicInteractions.stopInteractions(el);\n                publicInteractions.startInteractions(el);\n            }\n            promiseKeysResolves[lastKey]?.();\n        }.bind(this);\n        return {\n            /**\n             * @param {boolean} [refetch=false]\n             */\n            async getGMapAPIKey(refetch) {\n                if (refetch || !gmapAPIKeyProm) {\n                    gmapAPIKeyProm = (async () => {\n                        const data = await rpc(\"/website/google_maps_api_key\");\n                        return JSON.parse(data).google_maps_api_key || \"\";\n                    })();\n                }\n                return gmapAPIKeyProm;\n            },\n            /**\n             * @param {boolean} [editableMode=false]\n             * @param {boolean} [refetch=false]\n             */\n            async loadGMapAPI(editableMode, refetch) {\n                // Note: only need refetch to reload a configured key and load the\n                // library. If the library was loaded with a correct key and that the\n                // key changes meanwhile... it will not work but we can agree the user\n                // can bother to reload the page at that moment.\n                if (refetch || !(await gmapAPILoading)) {\n                    gmapAPILoading = (async () => {\n                        const key = await this.getGMapAPIKey(refetch);\n                        lastKey = key;\n\n                        if (key) {\n                            if (!promiseKeys[key]) {\n                                promiseKeys[key] = new Promise((resolve) => {\n                                    promiseKeysResolves[key] = resolve;\n                                });\n                                await loadJS(\n                                    `https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&callback=odoo_gmap_api_post_load&key=${encodeURIComponent(\n                                        key\n                                    )}`\n                                );\n                            }\n                            await promiseKeys[key];\n                            return key;\n                        } else {\n                            if (!editableMode && user.isAdmin) {\n                                const message = _t(\"Cannot load google map.\");\n                                const urlTitle = _t(\"Check your configuration.\");\n                                notification.add(\n                                    markup`<div>\n                                        <span>${message}</span><br/>\n                                        <a href=\"/odoo/action-website.action_website_configuration\">${urlTitle}</a>\n                                    </div>`,\n                                    { type: \"warning\", sticky: true }\n                                );\n                            }\n                            return false;\n                        }\n                    })();\n                }\n                return gmapAPILoading;\n            },\n            /**\n             * Send a request to the Google Maps API to test the validity of the given\n             * API key. Return an object with the error message if any, and a boolean\n             * that is true if the response from the API had a status of 200.\n             *\n             * Note: The response will be 200 so long as the API key has billing, Static\n             * API and Javascript API enabled. However, for our purposes, we also need\n             * the Places API enabled. To deal with that case, we perform a nearby\n             * search immediately after validation. If it fails, the error is handled\n             * and the dialog is re-opened.\n             * @see nearbySearch\n             * @see notifyGMapsError\n             *\n             * @param {string} key\n             * @returns {Promise<ApiKeyValidation>}\n             */\n            async validateGMapApiKey(key) {\n                if (key) {\n                    try {\n                        const response = await this.fetchGoogleMap(key);\n                        const isValid = response.status === 200;\n                        return {\n                            isValid,\n                            message: isValid\n                                ? undefined\n                                : _t(\n                                      \"Invalid API Key. The following error was returned by Google: %(error)s\",\n                                      { error: await response.text() }\n                                  ),\n                        };\n                    } catch {\n                        return {\n                            isValid: false,\n                            message: _t(\"Check your connection and try again\"),\n                        };\n                    }\n                } else {\n                    return { isValid: false };\n                }\n            },\n            /**\n             * Send a request to the Google Maps API, using the given API key, so as to\n             * get a response which can be used to test the validity of said key.\n             * This method is set apart so it can be overridden for testing.\n             *\n             * @param {string} key\n             * @returns {Promise<{ status: number }>}\n             */\n            async fetchGoogleMap(key) {\n                return await fetch(\n                    `https://maps.googleapis.com/maps/api/staticmap?center=belgium&size=10x10&key=${encodeURIComponent(\n                        key\n                    )}`\n                );\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"website_map\", websiteMapService);\n", "import { registry } from \"@web/core/registry\";\n\nexport const websiteMenusService = {\n    start() {\n        const updateCallbacks = new Set();\n        return {\n            updateCallbacks,\n            registerCallback(fn) {\n                updateCallbacks.add(fn);\n                return () => updateCallbacks.delete(fn);\n            },\n            triggerCallbacks() {\n                for (const callback of updateCallbacks) {\n                    callback();\n                }\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"website_menus\", websiteMenusService);\n", "import { jsToPyLocale } from \"@web/core/l10n/utils\";\nimport { registry } from \"@web/core/registry\";\nimport { user } from \"@web/core/user\";\n\nexport const websitePageService = {\n    start() {\n        const htmlEl = document.querySelector(\"html\");\n        // TODO this is duplicated in website_service.js at least... to share\n        const match = htmlEl.dataset.mainObject?.match(/(.+)\\((-?\\d+),(.*)\\)/);\n\n        return {\n            context: {\n                ...user.context,\n                website_id: htmlEl.dataset.websiteId | 0,\n                lang: jsToPyLocale(htmlEl.getAttribute(\"lang\")) || \"en_US\",\n                user_lang: user.context.lang,\n            },\n            mainObject: {\n                model: match && match[1],\n                id: match && match[2] | 0,\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"website_page\", websitePageService);\n", "// Definitely not the right location for this file !!!\n\n/**\n * @param {HTMLElement} element\n */\nexport function onceAllImagesLoaded(element) {\n    const imgEls = element.nodeName === \"IMG\" ? [element] : [...element.querySelectorAll(\"img\")];\n    const defs = imgEls.map((imgEl) => {\n        if (imgEl.complete) {\n            return; // Already loaded\n        }\n        return new Promise((resolve, reject) => {\n            imgEl.addEventListener(\"load\", resolve, { once: true });\n            imgEl.addEventListener(\"error\", reject, { once: true });\n        });\n    });\n    return Promise.all(defs);\n}\n", "import { loadJS } from \"@web/core/assets\";\nimport { hasTouch } from \"@web/core/browser/feature_detection\";\nimport { SIZES, utils as uiUtils } from \"@web/core/ui/ui_service\";\n\n/**\n * Takes care of any necessary setup for autoplaying video. In practice,\n * this method will load the youtube iframe API for mobile environments\n * because mobile environments don't support the youtube autoplay param\n * passed in the url.\n *\n * @param {string} src\n * @param {boolean} needCookiesApproval\n */\nexport function setupAutoplay(src, needCookiesApproval = false) {\n    const isYoutubeVideo = src.indexOf(\"youtube\") >= 0;\n    const isMobileEnv = uiUtils.getSize() <= SIZES.LG && hasTouch();\n\n    if (isYoutubeVideo && isMobileEnv && !window.YT && !needCookiesApproval) {\n        const oldOnYoutubeIframeAPIReady = window.onYouTubeIframeAPIReady;\n        const promise = new Promise((resolve) => {\n            window.onYouTubeIframeAPIReady = () => {\n                if (oldOnYoutubeIframeAPIReady) {\n                    oldOnYoutubeIframeAPIReady();\n                }\n                return resolve();\n            };\n        });\n        loadJS(\"https://www.youtube.com/iframe_api\");\n        return promise;\n    }\n    return Promise.resolve();\n}\n\n/**\n * @param {HTMLIframeElement} iframeEl - the iframe containing the video player\n */\nexport function triggerAutoplay(iframeEl) {\n    const isYoutubeVideo = iframeEl.src.indexOf(\"youtube\") >= 0;\n    const isMobileEnv = uiUtils.getSize() <= SIZES.LG && hasTouch();\n\n    // YouTube does not allow to auto-play video in mobile devices, so we\n    // have to play the video manually.\n    if (isYoutubeVideo && isMobileEnv && iframeEl.closest(\"[data-need-cookies-approval]\")) {\n        new window.YT.Player(iframeEl, {\n            events: {\n                onReady: (ev) => ev.target.playVideo(),\n            },\n        });\n    }\n}\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\n\nexport class AnnouncementScroll extends Interaction {\n    static selector = \".s_announcement_scroll\";\n\n    dynamicContent = {\n        _root: {\n            \"t-att-class\": () => ({\n                s_announcement_scroll_ready: this.announcementScrollReady,\n                s_announcement_scroll_page_scrolling: this.announcementScrollPageScrolling,\n            }),\n        },\n        _window: {\n            \"t-on-resize\": this.debounced(this.onResize, 100, { leading: true, trailing: true }),\n            \"t-on-scroll\": this.throttled(this.onScroll),\n        },\n        \".s_announcement_scroll_marquee_container\": {\n            \"t-att-style\": () => ({\n                transform: `translateX(${this.parallaxPosition}%)`,\n            }),\n        },\n    };\n\n    setup() {\n        this.marqueeContainerEl = this.el.querySelector(\".s_announcement_scroll_marquee_container\");\n        this.marqueeItemEl = this.el.querySelector(\".s_announcement_scroll_marquee_item\");\n        this.setParallaxPosition();\n    }\n\n    start() {\n        this.updateMarqueeLayout();\n        // The animation should start when the computation is done,\n        // else the first element will be already further than its clones\n        this.announcementScrollReady = true;\n        // TODO we might want to consider to make this automatic or something\n        this.updateContent();\n    }\n\n    destroy() {\n        this.undoMarqueeLayout();\n    }\n\n    /**\n     * Handles window resize events, updating the marquee layout.\n     */\n    onResize() {\n        this.announcementScrollReady = false;\n        this.updateContent();\n\n        this.updateMarqueeLayout();\n        this.announcementScrollReady = true;\n    }\n\n    /**\n     * Handles scroll events for parallax effect when enabled.\n     */\n    onScroll() {\n        // Needed even without parallax: scrolling, when the cursor passes over\n        // the element, it should not trigger the hover effect.\n        this.announcementScrollPageScrolling = true;\n        window.clearTimeout(this.scrollingTimeout);\n        this.scrollingTimeout = this.waitForTimeout(() => {\n            this.announcementScrollPageScrolling = false;\n        }, 200);\n\n        this.setParallaxPosition();\n    }\n\n    /**\n     * Sets the parallax position (if no parallax, reset it to the right static\n     * position).\n     */\n    setParallaxPosition() {\n        const MIN_LEFT_SHIFT = 50;\n\n        if (\n            !this.el.classList.contains(\"s_announcement_scroll_parallax\") ||\n            window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches === true\n        ) {\n            this.parallaxPosition = -MIN_LEFT_SHIFT;\n            return;\n        }\n\n        // One viewport worth of scroll (window.innerHeight) equals 50% parallax\n        // movement.\n        const PARALLAX_AMOUNT = 50;\n        const rect = this.el.getBoundingClientRect();\n        const startScroll = window.scrollY + rect.top - window.innerHeight;\n        const endScroll = window.scrollY + rect.bottom;\n        const progress = Math.min(\n            Math.max((window.scrollY - startScroll) / (endScroll - startScroll), 0),\n            1\n        );\n        if (this.el.classList.contains(\"s_announcement_scroll_direction_right\")) {\n            this.parallaxPosition = -MIN_LEFT_SHIFT - PARALLAX_AMOUNT + progress * PARALLAX_AMOUNT;\n        } else {\n            this.parallaxPosition = -MIN_LEFT_SHIFT - progress * PARALLAX_AMOUNT;\n        }\n    }\n\n    /**\n     * Undo everything done by previous @see updateMarqueeLayout calls.\n     */\n    undoMarqueeLayout() {\n        while (this.marqueeContainerEl.children.length > 1) {\n            this.marqueeContainerEl.lastChild.remove();\n        }\n        this.marqueeContainerEl.style.removeProperty(\"--marquee-item-size\");\n    }\n\n    /**\n     * Updates the marquee layout by calculating the items per container and\n     * cloning items as needed.\n     */\n    updateMarqueeLayout() {\n        const marqueeItemElWidth = this.marqueeItemEl.offsetWidth;\n        const itemsPerContainer = Math.ceil(\n            this.marqueeContainerEl.offsetWidth / marqueeItemElWidth\n        );\n        if (itemsPerContainer > 100) {\n            return;\n        }\n\n        this.undoMarqueeLayout();\n\n        this.marqueeContainerEl.style.setProperty(\"--marquee-item-size\", marqueeItemElWidth);\n\n        // * 2 to have 200% of the container width,\n        // + 1 for the reverse animation (see scss)\n        const cloneCount = itemsPerContainer * 2 + 1;\n        for (let i = 0; i < cloneCount; i++) {\n            const cloneEl = this.marqueeItemEl.cloneNode(true);\n            cloneEl.classList.add(\"s_announcement_scroll_marquee_item_clone\");\n            cloneEl.prepend(document.createTextNode(\"\\u00A0\")); // NBSP\n            this.marqueeContainerEl.appendChild(cloneEl);\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.announcement_scroll\", AnnouncementScroll);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { getCSSVariableValue } from \"@html_editor/utils/formatting\";\nimport { loadBundle } from \"@web/core/assets\";\n\nexport class Chart extends Interaction {\n    static selector = \".s_chart\";\n\n    setup() {\n        this.chart = null;\n        this.noAnimation = false;\n        this.style = window.getComputedStyle(document.documentElement);\n    }\n\n    async willStart() {\n        await loadBundle(\"web.chartjs_lib\");\n    }\n\n    start() {\n        const data = JSON.parse(this.el.dataset.data);\n        data.datasets.forEach((el) => {\n            el.backgroundColor = this.convertToCSS(el.backgroundColor);\n            el.borderColor = this.convertToCSS(el.borderColor);\n            el.borderWidth = this.el.dataset.borderWidth;\n        });\n\n        const radialAxis = {\n            beginAtZero: true,\n        };\n\n        const linearAxis = {\n            type: \"linear\",\n            stacked: this.el.dataset.stacked === \"true\",\n            beginAtZero: true,\n            min: parseInt(this.el.dataset.ticksMin),\n            max: parseInt(this.el.dataset.ticksMax),\n        };\n\n        const categoryAxis = {\n            type: \"category\",\n            stacked: this.el.dataset.stacked === \"true\",\n        };\n\n        const chartData = {\n            type: this.el.dataset.type,\n            data: data,\n            options: {\n                plugins: {\n                    legend: {\n                        display: this.el.dataset.legendPosition !== \"none\",\n                        position: this.el.dataset.legendPosition,\n                    },\n                    tooltip: {\n                        enabled: this.el.dataset.tooltipDisplay === \"true\",\n                        position: \"custom\",\n                    },\n                    title: {\n                        display: !!this.el.dataset.title,\n                        text: this.el.dataset.title,\n                    },\n                },\n                scales: {\n                    x: categoryAxis,\n                    y: linearAxis,\n                },\n                aspectRatio: 2,\n            },\n        };\n\n        if (this.el.dataset.type === \"radar\") {\n            chartData.options.scales = {\n                r: radialAxis,\n            };\n        } else if (this.el.dataset.type === \"horizontalBar\") {\n            chartData.type = \"bar\";\n            chartData.options.scales = {\n                x: linearAxis,\n                y: categoryAxis,\n            };\n            chartData.options.indexAxis = \"y\";\n        } else if ([\"pie\", \"doughnut\"].includes(this.el.dataset.type)) {\n            chartData.options.scales = {};\n            chartData.options.plugins.tooltip.callbacks = {\n                label: (tooltipItem) => {\n                    const label = tooltipItem.label;\n                    const secondLabel = tooltipItem.dataset.label;\n                    let final = label;\n                    if (label && secondLabel) {\n                        final = label + \" - \" + secondLabel;\n                    } else if (secondLabel) {\n                        final = secondLabel;\n                    }\n                    return final + \":\" + tooltipItem.formattedValue;\n                },\n            };\n        }\n\n        if (this.noAnimation) {\n            chartData.options.animation = { duration: 0 };\n        }\n\n        const canvasEl = this.el.querySelector(\"canvas\");\n        window.Chart.Tooltip.positioners.custom = (_, eventPosition) => eventPosition;\n        this.chart = new window.Chart(canvasEl, chartData);\n        this.registerCleanup(() => {\n            this.chart.destroy();\n            this.el.querySelectorAll(\".chartjs-size-monitor\").forEach((el) => el.remove());\n        });\n    }\n\n    /**\n     * @param {Array[string] || string} paramColor\n     */\n    convertToCSS(paramColor) {\n        return Array.isArray(paramColor)\n            ? paramColor.map((color) => this.convertToCSSColor(color))\n            : this.convertToCSSColor(paramColor);\n    }\n\n    /**\n     * @param {string} color\n     */\n    convertToCSSColor(color) {\n        return color ? getCSSVariableValue(color, this.style) || color : \"transparent\";\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.chart\", Chart);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { getCSSVariableValue, getHtmlStyle } from \"@html_editor/utils/formatting\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { isCSSColor } from \"@web/core/utils/colors\";\nimport { verifyHttpsUrl } from \"@website/utils/misc\";\n\nexport class Countdown extends Interaction {\n    static selector = \".s_countdown\";\n    dynamicContent = {\n        \".s_countdown_canvas_wrapper\": {\n            \"t-att-class\": () => ({\n                \"d-flex\": true,\n                \"justify-content-center\": true,\n            }),\n        },\n    };\n\n    setup() {\n        // Remove SVG previews (used to simulated canvas)\n        this.el.querySelectorAll(\"svg\").forEach((el) => el.parentNode.remove());\n\n        this.wrapperEl = this.el.querySelector(\".s_countdown_canvas_wrapper\");\n        this.hereBeforeTimerEnds = false;\n        this.endAction = this.el.dataset.endAction;\n        this.endTime = parseInt(this.el.dataset.endTime);\n        this.size = parseInt(this.el.dataset.size);\n        this.display = this.el.dataset.display;\n\n        if (!this.display && this.el.dataset.bsDisplay) {\n            // With the BS5 upgrade script of 16.0, countdowns' data-display may\n            // have been converted to data-bs-display by mistake. This will fix\n            // the DOM for good measures, maybe even allowing to remove this\n            // code in a few years as hopefully all current countdowns will have\n            // been removed or edited (or when a proper upgrade script in a\n            // future version of Odoo will be made, if necessary). TODO.\n            this.display = this.el.dataset.bsDisplay;\n            delete this.el.dataset.bsDisplay;\n            this.el.dataset.display = this.display;\n        }\n\n        this.defaultColor = \"rgba(0, 0, 0, 0)\";\n        this.layout = this.el.dataset.layout;\n        this.layoutBackground = this.el.dataset.layoutBackground;\n        this.progressBarStyle = this.el.dataset.progressBarStyle;\n        this.progressBarWeight = this.el.dataset.progressBarWeight;\n\n        this.layoutBackgroundColor = this.ensureCSSColor(this.el.dataset.layoutBackgroundColor);\n        this.progressBarColor = this.ensureCSSColor(this.el.dataset.progressBarColor);\n        this.textColor = this.ensureCSSColor(this.el.dataset.textColor);\n\n        this.onlyOneUnit = this.display === \"d\";\n        this.width = this.size;\n        if (this.layout === \"boxes\") {\n            this.width /= 1.75;\n        }\n        this.initTimeDiff();\n\n        this.render();\n\n        this.setInterval = setInterval(this.render.bind(this), 1000);\n    }\n\n    destroy() {\n        // The optional chaining is required because the queried element may not\n        // exist anymore if the interaction target has just been deleted\n        this.el.querySelector(\".s_countdown_canvas_wrapper\")?.classList.remove(\"d-none\");\n        clearInterval(this.setInterval);\n    }\n\n    /**\n     * Ensures the input is a valid CSS color\n     *\n     * @param {string} color\n     * @returns {string}\n     */\n    ensureCSSColor(color) {\n        if (isCSSColor(color)) {\n            return color;\n        }\n        return getCSSVariableValue(color, getHtmlStyle(document)) || this.defaultColor;\n    }\n\n    /**\n     * Handles the action that should be executed once the countdown ends.\n     */\n    handleEndCountdownAction() {\n        if (this.endAction === \"redirect\") {\n            const redirectUrl = verifyHttpsUrl(this.el.dataset.redirectUrl) || \"/\";\n            if (this.hereBeforeTimerEnds) {\n                this.waitForTimeout(() => (window.location = redirectUrl), 500);\n            } else {\n                if (!this.el.querySelector(\".s_countdown_end_redirect_message\")) {\n                    const container = this.el.querySelector(\n                        \":scope > .container, :scope > .container-fluid, :scope > .o_container_small\"\n                    );\n                    this.renderAt(\n                        \"website.s_countdown.end_redirect_message\",\n                        {\n                            redirectUrl: redirectUrl,\n                        },\n                        container\n                    );\n                }\n            }\n        } else if (this.endAction === \"message\" || this.endAction === \"message_no_countdown\") {\n            this.el.querySelector(\".s_countdown_end_message\")?.classList.remove(\"d-none\");\n        }\n        this.registerCleanup(() =>\n            this.el.querySelector(\".s_countdown_end_message\")?.classList.add(\"d-none\")\n        );\n    }\n\n    getDelta() {\n        return this.endTime - Date.now() / 1000;\n    }\n\n    createCanvasWrapper() {\n        const divEl = document.createElement(\"div\");\n        divEl.classList.add(\"s_countdown_canvas_flex\");\n        const canvasEl = document.createElement(\"canvas\");\n        canvasEl.classList.add(\"w-100\");\n        divEl.appendChild(canvasEl);\n        return divEl;\n    }\n\n    /**\n     * The timeDiff object will contains every visible time unit\n     * which will each contain its related canvas, total step, label..\n     */\n    initTimeDiff() {\n        const delta = this.getDelta();\n        this.timeDiff = [];\n        if (this.isUnitVisible(\"d\") && !(this.onlyOneUnit && delta < 86400)) {\n            const divEl = this.createCanvasWrapper();\n            this.insert(divEl, this.wrapperEl);\n            this.timeDiff.push({\n                canvas: divEl,\n                // There is no logical number of unit (total) on which day units\n                // can be compared against, so we use an arbitrary number.\n                total: 15,\n                label: _t(\"Days\"),\n                nbSeconds: 86400,\n            });\n        }\n        if (this.isUnitVisible(\"h\") || (this.onlyOneUnit && delta < 86400 && delta > 3600)) {\n            const divEl = this.createCanvasWrapper();\n            this.insert(divEl, this.wrapperEl);\n            this.timeDiff.push({\n                canvas: divEl,\n                total: 24,\n                label: _t(\"Hours\"),\n                nbSeconds: 3600,\n            });\n        }\n        if (this.isUnitVisible(\"m\") || (this.onlyOneUnit && delta < 3600 && delta > 60)) {\n            const divEl = this.createCanvasWrapper();\n            this.insert(divEl, this.wrapperEl);\n            this.timeDiff.push({\n                canvas: divEl,\n                total: 60,\n                label: _t(\"Minutes\"),\n                nbSeconds: 60,\n            });\n        }\n        if (this.isUnitVisible(\"s\") || (this.onlyOneUnit && delta < 60)) {\n            const divEl = this.createCanvasWrapper();\n            this.insert(divEl, this.wrapperEl);\n            this.timeDiff.push({\n                canvas: divEl,\n                total: 60,\n                label: _t(\"Seconds\"),\n                nbSeconds: 1,\n            });\n        }\n    }\n\n    updateTimediff() {\n        let delta = this.getDelta();\n        this.isFinished = delta < 0;\n        if (this.isFinished) {\n            for (const unitData of this.timeDiff) {\n                unitData.nb = 0;\n            }\n            return;\n        }\n        this.hereBeforeTimerEnds = true;\n        for (const unitData of this.timeDiff) {\n            unitData.nb = Math.floor(delta / unitData.nbSeconds);\n            delta -= unitData.nb * unitData.nbSeconds;\n        }\n    }\n\n    /**\n     * @param {string} unit - either \"d\", \"m\", \"h\", or \"s\"\n     * @returns {boolean}\n     */\n    isUnitVisible(unit) {\n        return this.display.includes(unit);\n    }\n\n    get shouldHideCountdown() {\n        return this.isFinished && this.el.classList.contains(\"hide-countdown\");\n    }\n\n    /**\n     * Draws the whole countdown, including one countdown for each time unit.\n     */\n    render() {\n        if (this.onlyOneUnit && this.getDelta() < this.timeDiff[0].nbSeconds) {\n            this.el.querySelector(\".s_countdown_canvas_flex\").remove();\n            this.initTimeDiff();\n        }\n        this.updateTimediff();\n\n        if (this.layout === \"text\") {\n            const canvasEls = this.el.querySelectorAll(\".s_countdown_canvas_flex\");\n            for (const canvasEl of canvasEls) {\n                canvasEl.classList.add(\"d-none\");\n            }\n            if (!this.textWrapperEl) {\n                this.textWrapperEl = document.createElement(\"span\");\n                this.textWrapperEl.classList.add(\"s_countdown_text_wrapper\", \"d-none\");\n                this.textWrapperEl.textContent = _t(\"Countdown ends in\");\n                const spanEl = document.createElement(\"span\");\n                spanEl.classList.add(\"s_countdown_text\", \"ms-1\");\n                this.textWrapperEl.appendChild(spanEl);\n                this.insert(this.textWrapperEl, this.wrapperEl);\n            }\n            this.textWrapperEl.classList.toggle(\"d-none\", this.shouldHideCountdown);\n\n            const countdownText = this.timeDiff.map((e) => e.nb + \" \" + e.label).join(\", \");\n            this.el.querySelector(\".s_countdown_text\").innerText = countdownText.toLowerCase();\n        } else {\n            for (const val of this.timeDiff) {\n                const canvas = val.canvas.querySelector(\"canvas\");\n                const ctx = canvas.getContext(\"2d\");\n                ctx.canvas.width = this.width;\n                ctx.canvas.height = this.size;\n                this.clearCanvas(ctx);\n\n                canvas.classList.toggle(\"d-none\", this.shouldHideCountdown);\n                if (this.shouldHideCountdown) {\n                    continue;\n                }\n\n                // Draw canvas elements\n                if (this.layoutBackground !== \"none\") {\n                    this.drawBgShape(ctx, this.layoutBackground === \"plain\");\n                }\n                this.drawText(canvas, val.nb, val.label, this.layoutBackground === \"plain\");\n                if (this.progressBarStyle === \"surrounded\") {\n                    this.drawProgressBarBg(ctx, this.progressBarWeight === \"thin\");\n                }\n                if (this.progressBarStyle !== \"none\") {\n                    this.drawProgressBar(ctx, val.nb, val.total, this.progressBarWeight === \"thin\");\n                }\n                val.canvas.classList.toggle(\"mx-1\", this.layout === \"boxes\");\n            }\n        }\n\n        if (this.isFinished) {\n            clearInterval(this.setInterval);\n            this.handleEndCountdownAction();\n        }\n    }\n\n    /**\n     * @param {CanvasRenderingContext2D} ctx - Context of the canvas\n     */\n    clearCanvas(ctx) {\n        ctx.clearRect(0, 0, this.size, this.size);\n    }\n\n    /**\n     * @param {HTMLCanvasElement} canvas\n     * @param {string} textNb - text to display in the center of the canvas, in big\n     * @param {string} textUnit - text to display bellow `textNb` in small\n     * @param {boolean} full - if true, the shape will be drawn up to the progressbar\n     */\n    drawText(canvas, textNb, textUnit, full = false) {\n        const ctx = canvas.getContext(\"2d\");\n        const nbSize = this.size / 4;\n        ctx.font = `${nbSize}px Arial`;\n        ctx.fillStyle = this.textColor;\n        ctx.textAlign = \"center\";\n        ctx.textBaseline = \"middle\";\n        ctx.fillText(textNb, canvas.width / 2, canvas.height / 2);\n\n        const unitSize = this.size / 12;\n        ctx.font = `${unitSize}px Arial`;\n        ctx.fillText(textUnit, canvas.width / 2, canvas.height / 2 + nbSize / 1.5, this.width);\n\n        if (\n            this.layout === \"boxes\" &&\n            this.layoutBackground !== \"none\" &&\n            this.progressBarStyle === \"none\"\n        ) {\n            let barWidth = this.size / (this.progressBarWeight === \"thin\" ? 31 : 10);\n            if (full) {\n                barWidth = 0;\n            }\n            ctx.beginPath();\n            ctx.moveTo(barWidth, this.size / 2);\n            ctx.lineTo(this.width - barWidth, this.size / 2);\n            ctx.stroke();\n        }\n    }\n\n    /**\n     * @param {CanvasRenderingContext2D} ctx - Context of the canvas\n     * @param {boolean} full - if true, the shape will be drawn up to the progressbar\n     */\n    drawBgShape(ctx, full = false) {\n        ctx.fillStyle = this.layoutBackgroundColor;\n        ctx.beginPath();\n        if (this.layout === \"circle\") {\n            let rayon = this.size / 2;\n            if (this.progressBarWeight === \"thin\") {\n                rayon -= full ? this.size / 29 : this.size / 15;\n            } else {\n                rayon -= full ? 0 : this.size / 10;\n            }\n            ctx.arc(this.size / 2, this.size / 2, rayon, 0, Math.PI * 2);\n            ctx.fill();\n        } else if (this.layout === \"boxes\") {\n            let barWidth = this.size / (this.progressBarWeight === \"thin\" ? 31 : 10);\n            if (full) {\n                barWidth = 0;\n            }\n\n            ctx.fillStyle = this.layoutBackgroundColor;\n            ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);\n            ctx.fill();\n\n            const gradient = ctx.createLinearGradient(0, this.width, 0, 0);\n            gradient.addColorStop(0, \"#ffffff24\");\n            gradient.addColorStop(1, this.layoutBackgroundColor);\n            ctx.fillStyle = gradient;\n            ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);\n            ctx.fill();\n            ctx.canvas.style.borderRadius = \"8px\";\n        }\n    }\n\n    /**\n     * @param {CanvasRenderingContext2D} ctx - Context of the canvas\n     * @param {number} nbUnit - how many unit should fill progress bar\n     * @param {number} totalUnit - number of unit to do a complete progress bar\n     * @param {boolean} useThinLine - if true, the progress bar will be thiner\n     */\n    drawProgressBar(ctx, nbUnit, totalUnit, useThinLine) {\n        ctx.strokeStyle = this.progressBarColor;\n        ctx.lineWidth = useThinLine ? this.size / 35 : this.size / 10;\n        if (this.layout === \"circle\") {\n            ctx.beginPath();\n            ctx.arc(\n                this.size / 2,\n                this.size / 2,\n                this.size / 2 - this.size / 20,\n                Math.PI / -2,\n                Math.PI * 2 * (nbUnit / totalUnit) + Math.PI / -2\n            );\n            ctx.stroke();\n        } else if (this.layout === \"boxes\") {\n            ctx.lineWidth *= 2;\n            let pc = (nbUnit / totalUnit) * 100;\n\n            // Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)\n            const linesCoordFuncs = [\n                (linePc) => [\n                    0 + ctx.lineWidth / 2,\n                    0,\n                    ((this.width - ctx.lineWidth / 2) * linePc) / 25 + ctx.lineWidth / 2,\n                    0,\n                ],\n                (linePc) => [\n                    this.width,\n                    0 + ctx.lineWidth / 2,\n                    this.width,\n                    ((this.size - ctx.lineWidth / 2) * linePc) / 25 + ctx.lineWidth / 2,\n                ],\n                (linePc) => [\n                    this.width -\n                        ((this.width - ctx.lineWidth / 2) * linePc) / 25 -\n                        ctx.lineWidth / 2,\n                    this.size,\n                    this.width - ctx.lineWidth / 2,\n                    this.size,\n                ],\n                (linePc) => [\n                    0,\n                    this.size - ((this.size - ctx.lineWidth / 2) * linePc) / 25 - ctx.lineWidth / 2,\n                    0,\n                    this.size - ctx.lineWidth / 2,\n                ],\n            ];\n            while (pc > 0 && linesCoordFuncs.length) {\n                const linePc = Math.min(pc, 25);\n                const lineCoord = linesCoordFuncs.shift()(linePc);\n                ctx.beginPath();\n                ctx.moveTo(lineCoord[0], lineCoord[1]);\n                ctx.lineTo(lineCoord[2], lineCoord[3]);\n                ctx.stroke();\n                pc -= linePc;\n            }\n        }\n    }\n\n    /**\n     * @param {CanvasRenderingContext2D} ctx - Context of the canvas\n     * @param {boolean} useThinLine\n     */\n    drawProgressBarBg(ctx, useThinLine) {\n        ctx.strokeStyle = this.progressBarColor;\n        ctx.globalAlpha = 0.2;\n        ctx.lineWidth = useThinLine ? this.size / 35 : this.size / 10;\n        if (this.layout === \"circle\") {\n            ctx.beginPath();\n            ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, 0, Math.PI * 2);\n            ctx.stroke();\n        } else if (this.layout === \"boxes\") {\n            ctx.lineWidth *= 2;\n\n            // Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)\n            const points = [\n                [0 + ctx.lineWidth / 2, 0, this.width, 0],\n                [this.width, 0 + ctx.lineWidth / 2, this.width, this.size],\n                [0, this.size, this.width - ctx.lineWidth / 2, this.size],\n                [0, 0, 0, this.size - ctx.lineWidth / 2],\n            ];\n            while (points.length) {\n                const point = points.shift();\n                ctx.beginPath();\n                ctx.moveTo(point[0], point[1]);\n                ctx.lineTo(point[2], point[3]);\n                ctx.stroke();\n            }\n        }\n        ctx.globalAlpha = 1;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.countdown\", Countdown);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { rpc } from \"@web/core/network/rpc\";\nimport { utils as uiUtils } from \"@web/core/ui/ui_service\";\nimport { uniqueId } from \"@web/core/utils/functions\";\nimport { renderToFragment } from \"@web/core/utils/render\";\nimport { verifyHttpsUrl } from \"@website/utils/misc\";\n\nimport { markup } from \"@odoo/owl\";\n\nconst DEFAULT_NUMBER_OF_ELEMENTS = 4;\nconst DEFAULT_NUMBER_OF_ELEMENTS_SM = 1;\n\nexport class DynamicSnippet extends Interaction {\n    static selector = \".s_dynamic_snippet\";\n    dynamicContent = {\n        \"[data-url]\": {\n            \"t-on-click\": this.callToAction,\n        },\n        _window: { \"t-on-resize\": this.throttled(this.render) },\n        \".missing_option_warning\": {\n            \"t-att-class\": () => ({\n                \"d-none\": !!this.data.length,\n            }),\n        },\n    };\n\n    setup() {\n        /**\n         * The dynamic filter data source data formatted with the chosen template.\n         * Can be accessed when overriding the _render_content() function in order to generate\n         * a new renderedContent from the original data.\n         *\n         * @type {*|jQuery.fn.init|jQuery|HTMLElement}\n         */\n        this.data = [];\n        this.renderedContentNode = document.createDocumentFragment();\n        this.uniqueId = uniqueId(\"s_dynamic_snippet_\");\n        this.templateKey = \"website.s_dynamic_snippet.grid\";\n        this.withSample = false;\n    }\n\n    async willStart() {\n        this.isSingleMode =\n            parseInt(this.el.dataset.numberOfRecords) === 1 && !this.el.dataset.filterId;\n        await this.fetchData();\n    }\n\n    start() {\n        this.render();\n    }\n\n    destroy() {\n        // Clear content.\n        const templateAreaEl = this.el.querySelector(\".dynamic_snippet_template\");\n        // Nested interactions are stopped implicitly.\n        templateAreaEl.replaceChildren();\n    }\n\n    /**\n     * To be overridden\n     * Check if additional configuration elements are required in order to fetch data.\n     */\n    isConfigComplete() {\n        const data = this.el.dataset;\n        const isSingleModeConfigComplete =\n            data.snippetModel && (!this.withSample ? data.snippetResId : true);\n        return !!(\n            data.templateKey && (this.isSingleMode ? isSingleModeConfigComplete : data.filterId)\n        );\n    }\n\n    /**\n     * To be overridden\n     * Provide a search domain if needed.\n     */\n    getSearchDomain() {\n        return [];\n    }\n\n    /**\n     * To be overridden\n     * Add custom parameters if needed.\n     */\n    getRpcParameters() {\n        return this.isSingleMode\n            ? {\n                  res_model: this.el.dataset.snippetModel,\n                  res_id: parseInt(this.el.dataset.snippetResId),\n              }\n            : {};\n    }\n\n    async fetchData() {\n        if (this.isConfigComplete()) {\n            const nodeData = this.el.dataset;\n            const filterFragments = await this.waitFor(\n                rpc(\n                    \"/website/snippet/filters\",\n                    Object.assign(\n                        {\n                            filter_id: parseInt(nodeData.filterId),\n                            template_key: nodeData.templateKey,\n                            limit: parseInt(nodeData.numberOfRecords),\n                            search_domain: this.getSearchDomain(),\n                            with_sample: this.withSample,\n                        },\n                        this.getRpcParameters(),\n                        JSON.parse(this.el.dataset?.customTemplateData || \"{}\")\n                    )\n                )\n            );\n            this.data = filterFragments.map(markup);\n        } else {\n            this.data = [];\n        }\n    }\n\n    /**\n     * To be overridden\n     * Prepare the content before rendering.\n     */\n    prepareContent() {\n        this.renderedContentNode = renderToFragment(this.templateKey, this.getQWebRenderOptions());\n    }\n\n    /**\n     * To be overridden\n     * Prepare QWeb options.\n     */\n    getQWebRenderOptions() {\n        const dataset = this.el.dataset;\n        const numberOfRecords = parseInt(dataset.numberOfRecords);\n        let numberOfElements;\n        if (uiUtils.isSmall()) {\n            numberOfElements =\n                parseInt(dataset.numberOfElementsSmallDevices) || DEFAULT_NUMBER_OF_ELEMENTS_SM;\n        } else {\n            numberOfElements = parseInt(dataset.numberOfElements) || DEFAULT_NUMBER_OF_ELEMENTS;\n        }\n        const chunkSize = numberOfRecords < numberOfElements ? numberOfRecords : numberOfElements;\n        return {\n            chunkSize: chunkSize,\n            data: this.data,\n            unique_id: this.uniqueId,\n            extraClasses: dataset.extraClasses || \"\",\n            columnClasses: dataset.columnClasses || \"\",\n        };\n    }\n\n    render() {\n        if (this.data.length > 0 || this.withSample) {\n            this.prepareContent();\n        } else {\n            this.renderedContentNode = document.createDocumentFragment();\n        }\n        this.renderContent();\n        // TODO What was this about ? Rendered content is already started.\n        // for (const childEl of this.el.children) {\n        //     this.services[\"public.interactions\"].startInteractions(childEl);\n        // }\n    }\n\n    renderContent() {\n        const templateAreaEl = this.el.querySelector(\".dynamic_snippet_template\");\n        this.services[\"public.interactions\"].stopInteractions(templateAreaEl);\n        templateAreaEl.replaceChildren(this.renderedContentNode);\n        this.el.classList.remove(\"o_dynamic_snippet_empty\");\n        // TODO this is probably not the only public widget which creates DOM\n        // which should be attached to another public widget. Maybe a generic\n        // method could be added to properly do this operation of DOM addition.\n        this.services[\"public.interactions\"].startInteractions(templateAreaEl);\n        // Same as above and probably should be done automatically for any\n        // bootstrap behavior (apparently needed since BS 5.3): start potential\n        // carousel in new content (according to their data-bs-ride and other\n        // dataset attributes). Note: done here and not in dynamic carousel\n        // extension, because: why not?\n        // (TODO review + See interaction with \"slider\" public widget).\n        this.waitForTimeout(() => {\n            templateAreaEl.querySelectorAll(\".carousel\").forEach((carouselEl) => {\n                if (carouselEl.dataset.bsInterval === \"0\") {\n                    delete carouselEl.dataset.bsRide;\n                    delete carouselEl.dataset.bsInterval;\n                }\n            });\n        }, 0);\n    }\n\n    /**\n     * Navigates to the call to action url.\n     *\n     * @param {Event} ev\n     */\n    callToAction(ev) {\n        window.location = verifyHttpsUrl(ev.currentTarget.dataset.url);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.dynamic_snippet\", DynamicSnippet);\n", "import { DynamicSnippet } from \"@website/snippets/s_dynamic_snippet/dynamic_snippet\";\nimport { registry } from \"@web/core/registry\";\n\nimport { utils as uiUtils } from \"@web/core/ui/ui_service\";\n\nexport class DynamicSnippetCarousel extends DynamicSnippet {\n    static selector = \".s_dynamic_snippet_carousel\";\n\n    setup() {\n        super.setup();\n        this.templateKey = \"website.s_dynamic_snippet.carousel\";\n    }\n\n    getQWebRenderOptions() {\n        const scrollMode = this.el.classList.contains(\"o_carousel_multi_items\") ? \"single\" : \"all\";\n        return Object.assign(super.getQWebRenderOptions(...arguments), {\n            interval: parseInt(this.el.dataset.carouselInterval),\n            rowPerSlide: parseInt(uiUtils.isSmall() ? 1 : this.el.dataset.rowPerSlide || 1),\n            arrowPosition: this.el.dataset.arrowPosition || \"\",\n            scrollMode: scrollMode,\n        });\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.dynamic_snippet_carousel\", DynamicSnippetCarousel);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { cloneContentEls } from \"@website/js/utils\";\n\nexport class EmbedCode extends Interaction {\n    static selector = \".s_embed_code\";\n\n    setup() {\n        this.embedCodeEl = this.el.querySelector(\".s_embed_code_embedded\");\n    }\n\n    destroy() {\n        // Just before entering edit mode, reinitialize the snippet's content,\n        // without <script> elements. This is both done so that scripts don't\n        // affect the DOM in edit mode, and to remove elements that would have\n        // been introduced by a script.\n        const templateContent = this.el.querySelector(\"template.s_embed_code_saved\").content;\n        this.embedCodeEl.replaceChildren(cloneContentEls(templateContent));\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.embed_code\", EmbedCode);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { clamp } from \"@web/core/utils/numbers\";\nimport { pick } from \"@web/core/utils/objects\";\n\nexport class FacebookPage extends Interaction {\n    static selector = \".o_facebook_page\";\n\n    setup() {\n        this.previousWidth = 0;\n        const params = pick(\n            this.el.dataset,\n            \"href\",\n            \"id\",\n            \"height\",\n            \"tabs\",\n            \"small_header\",\n            \"hide_cover\"\n        );\n        if (!params.href) {\n            return;\n        }\n        if (params.id) {\n            params.href = `https://www.facebook.com/${params.id}`;\n            delete params.id;\n        }\n\n        this.renderIframe(params);\n\n        this.resizeObserver = new ResizeObserver(\n            this.debounced(this.renderIframe.bind(this, params), 100)\n        );\n        this.resizeObserver.observe(this.el.parentElement);\n        this.registerCleanup(() => {\n            this.resizeObserver.disconnect();\n        });\n    }\n\n    /**\n     * Prepare iframe element & replace it with existing iframe.\n     *\n     * @param {Object} params\n     */\n    renderIframe(params) {\n        params.width = clamp(Math.floor(this.el.getBoundingClientRect().width), 180, 500);\n        if (this.previousWidth !== params.width) {\n            this.previousWidth = params.width;\n            const searchParams = new URLSearchParams(params);\n\n            const iframeEl = document.createElement(\"iframe\");\n            iframeEl.setAttribute(\"style\", \"border: none; overflow: hidden;\");\n            iframeEl.setAttribute(\"aria-label\", _t(\"Facebook\"));\n            iframeEl.height = params.height;\n            iframeEl.width = params.width;\n\n            this.el.replaceChildren(iframeEl);\n            this.registerCleanup(() => {\n                iframeEl.remove();\n            });\n\n            const src = \"https://www.facebook.com/plugins/page.php?\" + searchParams;\n            this.services.website_cookies.manageIframeSrc(iframeEl, src);\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.facebook_page\", FacebookPage);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class FaqHorizontal extends Interaction {\n    static selector = \".s_faq_horizontal\";\n    dynamicContent = {\n        \".s_faq_horizontal_entry_title\": {\n            \"t-att-style\": () => ({\n                top: `${this.offset}px`,\n                maxHeight: `calc(100vh - ${this.offset + 40}px)`,\n            }),\n        },\n    };\n\n    setup() {\n        this.offset = 16;\n    }\n\n    start() {\n        this.updateTitlesPosition();\n        this.registerCleanup(\n            this.services.website_menus.registerCallback(this.updateTitlesPosition.bind(this))\n        );\n    }\n\n    updateTitlesPosition() {\n        let offset = 16; // Add 1rem equivalent in px to provide a visual gap by default\n        for (const el of this.el.ownerDocument.querySelectorAll(\".o_top_fixed_element\")) {\n            offset += el.getBoundingClientRect().bottom;\n        }\n        this.offset = offset;\n        this.updateContent();\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.faq_horizontal\", FaqHorizontal);\n\nregistry.category(\"public.interactions.edit\").add(\"website.faq_horizontal\", {\n    Interaction: FaqHorizontal,\n});\n", "import { convertNumericToUnit, getHtmlStyle } from \"@html_editor/utils/formatting\";\nimport { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class FloatingBlocks extends Interaction {\n    static selector = \".s_floating_blocks\";\n\n    dynamicContent = {\n        _window: {\n            \"t-on-resize\": this.debounced(this.onResize, 100),\n            \"t-on-scroll\": this.throttled(this.onScroll),\n        },\n        \".s_floating_blocks_block\": {\n            \"t-att-style\": (blockEl) => ({\n                opacity: \"1\",\n                top: this.boxesTops.get(blockEl),\n                transform: this.boxesTransforms.get(blockEl),\n            }),\n            \"t-on-keydown\": this.onKeydown,\n        },\n    };\n\n    setup() {\n        this.boxScaleStep = 0.02;\n        this.maximalScale = 0.98;\n        this.minimalScale = this.maximalScale;\n\n        this.boxesEls = this.el.querySelectorAll(\".s_floating_blocks_block\");\n\n        this.initialGap = 16; // Provide a visual gap by default\n        this.stackingGap = this.initialGap * 0.8;\n\n        this.boxesTops = new WeakMap();\n        this.boxesTransforms = new WeakMap();\n        this.boxesToAnimate = [];\n        this.boxesScaleStep = [];\n        this.boxesScaleProp = [];\n    }\n\n    start() {\n        this.adaptToHeaderChange();\n        this.registerCleanup(\n            this.services.website_menus.registerCallback(this.adaptToHeaderChange.bind(this))\n        );\n\n        if (this.boxesEls.length >= 2) {\n            // The last block does not need to be animated\n            this.boxesToAnimate = Array.from(this.boxesEls).slice(0, -1);\n\n            // Calculate the minimal scale based on number of animated cards.\n            // Each card decreases the scale by `this.boxScaleStep`.\n            this.minimalScale = Math.max(\n                this.maximalScale - this.boxScaleStep * (this.boxesToAnimate.length - 1),\n                0.7 // Safe-net to ensure we don't go below 0.7 for very large numbers of cards\n            );\n\n            // Calculate scale factors for each card\n            this.boxesScaleStep = this.calculateScaleFactors();\n\n            this.onResize();\n            this.onScroll();\n            this.updateContent();\n        }\n    }\n\n    /**\n     * Calculates proportional scale factors for each card and pre-compute\n     * transforms.\n     *\n     * @returns {Array<number>} Array of scale factors\n     */\n    calculateScaleFactors() {\n        const boxesLength = this.boxesToAnimate.length;\n        const boxesScaleStep = [];\n        if (boxesLength === 0) {\n            return boxesScaleStep;\n        }\n\n        // If only one card to animate, use this.maximalScale\n        if (boxesLength === 1) {\n            boxesScaleStep.push(this.maximalScale);\n            this.boxesScaleProp[0] = `scale3d(${this.maximalScale}, ${this.maximalScale}, ${this.maximalScale})`;\n\n            return boxesScaleStep;\n        }\n\n        // Calculate the step between each card's scale\n        const scaleStep = (this.maximalScale - this.minimalScale) / (boxesLength - 1);\n\n        // Assign proportional scale factors and pre-compute transforms\n        for (let i = 0; i < boxesLength; i++) {\n            const scale = this.minimalScale + scaleStep * i;\n            boxesScaleStep.push(scale);\n\n            // Pre-compute the transform string for this scale\n            this.boxesScaleProp[i] = `scale3d(${scale}, ${scale}, ${scale})`;\n        }\n\n        return boxesScaleStep;\n    }\n\n    /**\n     * Updates stacking offset of each box.\n     */\n    adaptToHeaderChange() {\n        let top = this.initialGap;\n        for (const el of this.el.ownerDocument.querySelectorAll(\".o_top_fixed_element\")) {\n            top += el.offsetHeight;\n        }\n        this.boxesEls.forEach((boxEl, index) => {\n            this.boxesTops.set(boxEl, `${top + this.stackingGap * index}px`);\n        });\n    }\n\n    /**\n     * Adapts the zoom animation values.\n     */\n    updateZoom() {\n        const scrollTop = window.scrollY;\n        this.boxesToAnimate.forEach((blockEl, i) => {\n            const blockGap = scrollTop - this.snippetOffset - this.snippetHeight * i;\n            const transformValue = this.computeTransform(blockGap, i);\n            this.boxesTransforms.set(blockEl, transformValue);\n        });\n    }\n\n    /**\n     * Computes a block transform based on given environment values.\n     *\n     * @param {number} blockGap - Gap between the block and its target position\n     * @param {number} index - Index of the block in boxesToAnimate array\n     * @returns {string} CSS transform value\n     */\n    computeTransform(blockGap, index) {\n        // If block is at or above initial position, use the initial transform\n        if (blockGap <= 0) {\n            return \"scale3d(1, 1, 1)\";\n        }\n\n        const targetScale = this.boxesScaleStep[index];\n        const scale = Math.max(targetScale, 1 - blockGap * this.snippetScaleFactor);\n\n        // Return pre-computed value if it's close enough to the target scale\n        if (Math.abs(scale - targetScale) < 0.001) {\n            return this.boxesScaleProp[index];\n        }\n\n        // Calculate and return the transform for intermediate values\n        return `scale3d(${scale}, ${scale}, ${scale})`;\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * On page resize, updates the animation.\n     */\n    onResize() {\n        this.viewportHeight = window.innerHeight;\n        this.snippetHeight = Math.min(\n            this.boxesEls[0]?.offsetHeight || 0,\n            window.innerHeight - 100\n        );\n        this.snippetOffset = this.el.getBoundingClientRect().y + window.scrollY;\n        this.snippetScaleFactor = 1 / (this.snippetHeight * 12);\n\n        this.updateZoom();\n    }\n\n    /**\n     * On page scroll, updates the animation.\n     */\n    onScroll() {\n        this.updateZoom();\n    }\n    /**\n     * Support Shift+Tab navigation\n     *\n     * @param {KeyboardEvent} ev\n     */\n    onKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        if (hotkey === \"shift+tab\") {\n            this.addListener(ev.currentTarget, \"focusout\", this.onShiftTabFocusout.bind(this), {\n                once: true,\n            });\n        }\n    }\n    onShiftTabFocusout(ev) {\n        if (\n            !ev.relatedTarget ||\n            ev.relatedTarget.closest(\".s_floating_blocks_block\") === ev.currentTarget\n        ) {\n            return;\n        }\n        // Account for `.gap-5` on the container.\n        const gap = convertNumericToUnit(3, \"rem\", \"px\", getHtmlStyle(document));\n        scrollTo(0, window.scrollY - (this.snippetHeight + gap));\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.floating_blocks\", FloatingBlocks);\n", "/* global google */\n\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class GoogleMap extends Interaction {\n    static selector = \".s_google_map\";\n    dynamicContent = {\n        _window: {\n            \"t-on-resize\": () => {\n                if (this.gps) {\n                    this.map.setCenter(this.gps);\n                }\n            },\n        },\n    };\n\n    mapColors = {\n        // prettier-ignore\n        lightMonoMap: [{ \"featureType\": \"administrative.locality\", \"elementType\": \"all\", \"stylers\": [{ \"hue\": \"#2c2e33\" }, { \"saturation\": 7 }, { \"lightness\": 19 }, { \"visibility\": \"on\" }] }, { \"featureType\": \"landscape\", \"elementType\": \"all\", \"stylers\": [{ \"hue\": \"#ffffff\" }, { \"saturation\": -100 }, { \"lightness\": 100 }, { \"visibility\": \"simplified\" }] }, { \"featureType\": \"poi\", \"elementType\": \"all\", \"stylers\": [{ \"hue\": \"#ffffff\" }, { \"saturation\": -100 }, { \"lightness\": 100 }, { \"visibility\": \"off\" }] }, { \"featureType\": \"road\", \"elementType\": \"geometry\", \"stylers\": [{ \"hue\": \"#bbc0c4\" }, { \"saturation\": -93 }, { \"lightness\": 31 }, { \"visibility\": \"simplified\" }] }, { \"featureType\": \"road\", \"elementType\": \"labels\", \"stylers\": [{ \"hue\": \"#bbc0c4\" }, { \"saturation\": -93 }, { \"lightness\": 31 }, { \"visibility\": \"on\" }] }, { \"featureType\": \"road.arterial\", \"elementType\": \"labels\", \"stylers\": [{ \"hue\": \"#bbc0c4\" }, { \"saturation\": -93 }, { \"lightness\": -2 }, { \"visibility\": \"simplified\" }] }, { \"featureType\": \"road.local\", \"elementType\": \"geometry\", \"stylers\": [{ \"hue\": \"#e9ebed\" }, { \"saturation\": -90 }, { \"lightness\": -8 }, { \"visibility\": \"simplified\" }] }, { \"featureType\": \"transit\", \"elementType\": \"all\", \"stylers\": [{ \"hue\": \"#e9ebed\" }, { \"saturation\": 10 }, { \"lightness\": 69 }, { \"visibility\": \"on\" }] }, { \"featureType\": \"water\", \"elementType\": \"all\", \"stylers\": [{ \"hue\": \"#e9ebed\" }, { \"saturation\": -78 }, { \"lightness\": 67 }, { \"visibility\": \"simplified\" }] }],\n        // prettier-ignore\n        lillaMap: [{ elementType: \"labels\", stylers: [{ saturation: -20 }] }, { featureType: \"poi\", elementType: \"labels\", stylers: [{ visibility: \"off\" }] }, { featureType: 'road.highway', elementType: 'labels', stylers: [{ visibility: \"off\" }] }, { featureType: \"road.local\", elementType: \"labels.icon\", stylers: [{ visibility: \"off\" }] }, { featureType: \"road.arterial\", elementType: \"labels.icon\", stylers: [{ visibility: \"off\" }] }, { featureType: \"road\", elementType: \"geometry.stroke\", stylers: [{ visibility: \"off\" }] }, { featureType: \"transit\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"poi\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"poi.government\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"poi.sport_complex\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"poi.attraction\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"poi.business\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"transit\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"transit.station\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"landscape\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"road\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"road.highway\", elementType: \"geometry.fill\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }, { featureType: \"water\", elementType: \"geometry\", stylers: [{ hue: '#2d313f' }, { visibility: \"on\" }, { lightness: 5 }, { saturation: -20 }] }],\n        // prettier-ignore\n        blueMap: [{ stylers: [{ hue: \"#00ffe6\" }, { saturation: -20 }] }, { featureType: \"road\", elementType: \"geometry\", stylers: [{ lightness: 100 }, { visibility: \"simplified\" }] }, { featureType: \"road\", elementType: \"labels\", stylers: [{ visibility: \"off\" }] }],\n        // prettier-ignore\n        retroMap: [{ \"featureType\": \"administrative\", \"elementType\": \"all\", \"stylers\": [{ \"visibility\": \"on\" }, { \"lightness\": 33 }] }, { \"featureType\": \"landscape\", \"elementType\": \"all\", \"stylers\": [{ \"color\": \"#f2e5d4\" }] }, { \"featureType\": \"poi.park\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#c5dac6\" }] }, { \"featureType\": \"poi.park\", \"elementType\": \"labels\", \"stylers\": [{ \"visibility\": \"on\" }, { \"lightness\": 20 }] }, { \"featureType\": \"road\", \"elementType\": \"all\", \"stylers\": [{ \"lightness\": 20 }] }, { \"featureType\": \"road.highway\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#c5c6c6\" }] }, { \"featureType\": \"road.arterial\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#e4d7c6\" }] }, { \"featureType\": \"road.local\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#fbfaf7\" }] }, { \"featureType\": \"water\", \"elementType\": \"all\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#acbcc9\" }] }],\n        // prettier-ignore\n        flatMap: [{ \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"road\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#ffffff\" }] }, { \"featureType\": \"road.arterial\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#fee379\" }] }, { \"featureType\": \"road.highway\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#fee379\" }] }, { \"featureType\": \"landscape\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#f3f4f4\" }] }, { \"featureType\": \"water\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#7fc8ed\" }] }, {}, { \"featureType\": \"road\", \"elementType\": \"labels\", \"stylers\": [{ \"visibility\": \"on\" }] }, { \"featureType\": \"poi.park\", \"elementType\": \"geometry.fill\", \"stylers\": [{ \"visibility\": \"on\" }, { \"color\": \"#83cead\" }] }, { \"elementType\": \"labels\", \"stylers\": [{ \"visibility\": \"on\" }] }, { \"featureType\": \"landscape.man_made\", \"elementType\": \"geometry\", \"stylers\": [{ \"weight\": 0.9 }, { \"visibility\": \"off\" }] }],\n        // prettier-ignore\n        cobaltMap: [{ \"featureType\": \"all\", \"elementType\": \"all\", \"stylers\": [{ \"invert_lightness\": true }, { \"saturation\": 10 }, { \"lightness\": 30 }, { \"gamma\": 0.5 }, { \"hue\": \"#435158\" }] }],\n        // prettier-ignore\n        cupertinoMap: [{ \"featureType\": \"water\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#a2daf2\" }] }, { \"featureType\": \"landscape.man_made\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#f7f1df\" }] }, { \"featureType\": \"landscape.natural\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#d0e3b4\" }] }, { \"featureType\": \"landscape.natural.terrain\", \"elementType\": \"geometry\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"poi.park\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#bde6ab\" }] }, { \"featureType\": \"poi\", \"elementType\": \"labels\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"poi.medical\", \"elementType\": \"geometry\", \"stylers\": [{ \"color\": \"#fbd3da\" }] }, { \"featureType\": \"poi.business\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"road\", \"elementType\": \"geometry.stroke\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"road\", \"elementType\": \"labels\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"road.highway\", \"elementType\": \"geometry.fill\", \"stylers\": [{ \"color\": \"#ffe15f\" }] }, { \"featureType\": \"road.highway\", \"elementType\": \"geometry.stroke\", \"stylers\": [{ \"color\": \"#efd151\" }] }, { \"featureType\": \"road.arterial\", \"elementType\": \"geometry.fill\", \"stylers\": [{ \"color\": \"#ffffff\" }] }, { \"featureType\": \"road.local\", \"elementType\": \"geometry.fill\", \"stylers\": [{ \"color\": \"black\" }] }, { \"featureType\": \"transit.station.airport\", \"elementType\": \"geometry.fill\", \"stylers\": [{ \"color\": \"#cfb2db\" }] }],\n        // prettier-ignore\n        carMap: [{ \"featureType\": \"administrative\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"poi\", \"stylers\": [{ \"visibility\": \"simplified\" }] }, { \"featureType\": \"road\", \"stylers\": [{ \"visibility\": \"simplified\" }] }, { \"featureType\": \"water\", \"stylers\": [{ \"visibility\": \"simplified\" }] }, { \"featureType\": \"transit\", \"stylers\": [{ \"visibility\": \"simplified\" }] }, { \"featureType\": \"landscape\", \"stylers\": [{ \"visibility\": \"simplified\" }] }, { \"featureType\": \"road.highway\", \"stylers\": [{ \"visibility\": \"off\" }] }, { \"featureType\": \"road.local\", \"stylers\": [{ \"visibility\": \"on\" }] }, { \"featureType\": \"road.highway\", \"elementType\": \"geometry\", \"stylers\": [{ \"visibility\": \"on\" }] }, { \"featureType\": \"water\", \"stylers\": [{ \"color\": \"#84afa3\" }, { \"lightness\": 52 }] }, { \"stylers\": [{ \"saturation\": -77 }] }, { \"featureType\": \"road\" }],\n        // prettier-ignore\n        bwMap: [{ stylers: [{ hue: \"#00ffe6\" }, { saturation: -100 }] }, { featureType: \"road\", elementType: \"geometry\", stylers: [{ lightness: 100 }, { visibility: \"simplified\" }] }, { featureType: \"road\", elementType: \"labels\", stylers: [{ visibility: \"off\" }] }],\n    };\n\n    setup() {\n        this.canStart = false;\n        this.canSpecifyKey = false;\n        this.map = undefined;\n        this.gps = undefined;\n    }\n\n    async willStart() {\n        if (typeof google !== \"object\" || typeof google.maps !== \"object\") {\n            // @TODO mysterious-egg: this would not be needed if we didn't\n            // duplicate the API loading:\n            const refetch = window.top.refetchGoogleMaps;\n            window.top.refetchGoogleMaps = false;\n            await this.services.website_map.loadGMapAPI(this.canSpecifyKey, refetch);\n            return;\n        }\n        this.canStart = true;\n    }\n\n    start() {\n        if (!this.canStart) {\n            return;\n        }\n        // Define a default map's colors set\n        const std = [];\n        new google.maps.StyledMapType(std, { name: \"Std Map\" });\n\n        // Default options, will be overwritten by the user\n        const myOptions = {\n            zoom: 12,\n            center: new google.maps.LatLng(50.854975, 4.3753899),\n            mapTypeId: google.maps.MapTypeId.ROADMAP,\n            panControl: false,\n            zoomControl: false,\n            mapTypeControl: false,\n            streetViewControl: false,\n            scrollwheel: false,\n            mapTypeControlOptions: {\n                mapTypeIds: [google.maps.MapTypeId.ROADMAP, \"map_style\"],\n            },\n        };\n\n        // Render Map\n        const mapC = this.el.querySelector(\".map_container\");\n        const map = new google.maps.Map(mapC, myOptions);\n\n        // Update GPS position\n        const p = this.el.dataset.mapGps.substring(1).slice(0, -1).split(\",\");\n\n        this.gps = new google.maps.LatLng(p[0], p[1]);\n        map.setCenter(this.gps);\n\n        // Create Marker & Infowindow\n        const markerOptions = {\n            map: map,\n            animation: google.maps.Animation.DROP,\n            position: new google.maps.LatLng(p[0], p[1]),\n        };\n        if (this.el.dataset.pinStyle === \"flat\") {\n            markerOptions.icon = \"/website/static/src/img/snippets_thumbs/s_google_map_marker.png\";\n        }\n        new google.maps.Marker(markerOptions);\n\n        map.setMapTypeId(google.maps.MapTypeId[this.el.dataset.mapType]); // Update Map Type\n        map.setZoom(parseInt(this.el.dataset.mapZoom)); // Update Map Zoom\n\n        // Update Map Color\n        const mapColorAttr = this.el.dataset.mapColor;\n        if (mapColorAttr) {\n            const mapColor = this.mapColors[mapColorAttr];\n            map.mapTypes.set(\n                \"map_style\",\n                new google.maps.StyledMapType(mapColor, { name: \"Styled Map\" })\n            );\n            map.setMapTypeId(\"map_style\");\n        }\n        this.map = map;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.google_map\", GoogleMap);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { uniqueId } from \"@web/core/utils/functions\";\nimport { renderToElement } from \"@web/core/utils/render\";\n\nexport class Gallery extends Interaction {\n    static selector = \".s_image_gallery:not(.o_slideshow)\";\n    dynamicContent = {\n        img: {\n            \"t-on-click\": this.onClickImg,\n        },\n    };\n\n    setup() {\n        this.modalEl = null;\n        this.originalSources = [...this.el.querySelectorAll(\"img\")].map((img) =>\n            img.getAttribute(\"src\")\n        );\n    }\n\n    /**\n     * Called when an image is clicked. Opens a dialog to browse all the images\n     * with a bigger size.\n     *\n     * @param {Event} ev\n     */\n    onClickImg(ev) {\n        const clickedEl = ev.currentTarget;\n        if (this.modalEl || clickedEl.matches(\"a > img\")) {\n            return;\n        }\n\n        let imageEls = this.el.querySelectorAll(\"img\");\n        const currentImageEl = clickedEl.closest(\"img\");\n        const currentImageIndex = [...imageEls].indexOf(currentImageEl);\n        // We need to reset the images to their original source because it might\n        // have been changed by a mouse event (e.g. \"hover effect\" animation).\n        imageEls = [...imageEls].map((el, i) => {\n            const cloneEl = el.cloneNode(true);\n            cloneEl.src = this.originalSources[i];\n            return cloneEl;\n        });\n\n        const size = 0.8;\n        const dimensions = {\n            min_width: Math.round(window.innerWidth * size * 0.9),\n            min_height: Math.round(window.innerHeight * size),\n            max_width: Math.round(window.innerWidth * size * 0.9),\n            max_height: Math.round(window.innerHeight * size),\n            width: Math.round(window.innerWidth * size * 0.9),\n            height: Math.round(window.innerHeight * size),\n        };\n\n        const milliseconds = this.el.dataset.interval || false;\n        const lightboxTemplate =\n            this.el.dataset.vcss === \"002\"\n                ? \"website.gallery.s_image_gallery_mirror.lightbox\"\n                : \"website.gallery.slideshow.lightbox\";\n\n        this.modalEl = renderToElement(lightboxTemplate, {\n            images: imageEls,\n            index: currentImageIndex,\n            dim: dimensions,\n            interval: milliseconds || 0,\n            ride: !milliseconds ? \"false\" : \"carousel\",\n            id: uniqueId(\"slideshow_\"),\n        });\n\n        this.onModalKeydownBound = this.onModalKeydown.bind(this);\n\n        this.modalEl.addEventListener(\"hidden.bs.modal\", () => {\n            this.modalEl.classList.add(\"d-none\");\n            for (const backdropEl of this.modalEl.querySelectorAll(\".modal-backdrop\")) {\n                backdropEl.remove(); // bootstrap leaves a modal-backdrop\n            }\n            const slideshowEl = this.modalEl.querySelector(\".modal-body.o_slideshow\");\n            this.services[\"public.interactions\"].stopInteractions(slideshowEl);\n            this.modalEl.removeEventListener(\"keydown\", this.onModalKeydownBound);\n            this.modalEl.remove();\n            this.modalEl = undefined;\n        });\n\n        this.modalEl.addEventListener(\n            \"shown.bs.modal\",\n            () => {\n                const slideshowEl = this.modalEl.querySelector(\".modal-body.o_slideshow\");\n                this.services[\"public.interactions\"].startInteractions(slideshowEl);\n                this.modalEl.addEventListener(\"keydown\", this.onModalKeydownBound);\n            },\n            { once: true }\n        );\n\n        this.insert(this.modalEl, document.body);\n        const modalBS = new Modal(this.modalEl, { keyboard: true, backdrop: true });\n        modalBS.show();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onModalKeydown(ev) {\n        if (ev.key === \"ArrowLeft\" || ev.key === \"ArrowRight\") {\n            const side = ev.key === \"ArrowLeft\" ? \"prev\" : \"next\";\n            this.modalEl.querySelector(`.carousel-control-${side}`).click();\n        }\n        if (ev.key === \"Escape\") {\n            // If the user is connected as an editor, prevent the backend header from collapsing.\n            ev.stopPropagation();\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.gallery\", Gallery);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { isVisible } from \"@html_editor/utils/dom_info\";\n\nexport class GallerySlider extends Interaction {\n    static selector = \".o_slideshow\";\n    dynamicContent = {\n        \".carousel\": {\n            \"t-on-slide.bs.carousel\": this.onSlideCarousel,\n            \"t-on-slid.bs.carousel\": this.onSlidCarousel,\n        },\n        \".carousel .carousel-indicators\": {\n            \"t-on-click\": this.onClickIndicator,\n        },\n    };\n\n    setup() {\n        this.hideOnClickIndicator = true;\n        this.carouselEl = this.el.classList.contains(\"carousel\")\n            ? this.el\n            : this.el.querySelector(\".carousel\");\n        this.indicatorEl = this.carouselEl?.querySelector(\".carousel-indicators\");\n        if (this.indicatorEl) {\n            this.prevEl = this.indicatorEl.querySelector(\"li.o_indicators_left\");\n            this.nextEl = this.indicatorEl.querySelector(\"li.o_indicators_right\");\n            if (this.prevEl) {\n                this.prevEl.style.visibility = \"\"; // force visibility as some databases have it hidden\n            }\n            if (this.nextEl) {\n                this.nextEl.style.visibility = \"\";\n            }\n            this.liEls = this.indicatorEl.querySelectorAll(\"li[data-bs-slide-to]\");\n            let indicatorWidth = this.indicatorEl.getBoundingClientRect().width;\n            if (indicatorWidth === 0) {\n                // An ancestor may be hidden so we try to find it and make it\n                // visible just to take the correct width.\n                let indicatorParentEl = this.indicatorEl.parentElement;\n                while (indicatorParentEl) {\n                    if (!isVisible(indicatorParentEl)) {\n                        if (!indicatorParentEl.style.display) {\n                            indicatorParentEl.style.display = \"block\";\n                            indicatorWidth = this.indicatorEl.getBoundingClientRect().width;\n                            indicatorParentEl.style.display = \"\";\n                        }\n                        break;\n                    }\n                    indicatorParentEl = indicatorParentEl.parentElement;\n                }\n            }\n            this.nbPerPage =\n                Math.floor(\n                    indicatorWidth /\n                        (this.liEls.length > 0\n                            ? this.liEls[0].getBoundingClientRect().width\n                            : undefined)\n                ) - 3; // - navigator - 1 to leave some space\n            this.realNbPerPage = this.nbPerPage || 1;\n            this.nbPages = Math.ceil(this.liEls.length / this.realNbPerPage);\n        }\n        this.onSlidCarousel();\n    }\n\n    destroy() {\n        if (this.prevEl) {\n            this.indicatorEl.prepend(this.prevEl);\n        }\n        if (this.nextEl) {\n            this.indicatorEl.append(this.nextEl);\n        }\n    }\n\n    onSlideCarousel() {\n        if (!this.carouselEl || !this.liEls) {\n            return;\n        }\n        this.waitForTimeout(() => {\n            const itemEl = this.carouselEl.querySelector(\n                \".carousel-inner .carousel-item-prev, .carousel-inner .carousel-item-next\"\n            );\n            if (!itemEl) {\n                return;\n            }\n            const index = [...itemEl.parentElement.children].indexOf(itemEl);\n            for (const liEl of this.liEls) {\n                liEl.classList.remove(\"active\");\n            }\n            const selectedLiEl = [...this.liEls].find((el) => el.dataset.bsSlideTo === `${index}`);\n            selectedLiEl?.classList.add(\"active\");\n        }, 0);\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onClickIndicator(ev) {\n        // Delegate from this.indicatorEl.\n        const dispatchedEl = ev.target.closest(\"li:not([data-bs-slide-to])\");\n        if (!dispatchedEl || dispatchedEl.parentElement !== this.indicatorEl) {\n            return;\n        }\n        this.page += dispatchedEl.classList.contains(\"o_indicators_left\") ? -1 : 1;\n        this.page = Math.max(0, Math.min(this.nbPages - 1, this.page)); // should not be necessary\n        window.Carousel.getOrCreateInstance(this.carouselEl).to(this.page * this.realNbPerPage);\n        // We dont use hide() before the slide animation in the editor because there is a traceback\n        // TO DO: fix this traceback\n        if (this.hideOnClickIndicator) {\n            this.hide();\n        }\n    }\n\n    hide() {\n        for (let i = 0; i < this.liEls?.length; i++) {\n            this.liEls[i].classList.toggle(\n                \"d-none\",\n                i < this.page * this.nbPerPage || i >= (this.page + 1) * this.nbPerPage\n            );\n        }\n        if (this.prevEl) {\n            if (this.page <= 0) {\n                this.prevEl.remove();\n            } else {\n                this.prevEl.classList.remove(\"d-none\");\n                this.indicatorEl.insertAdjacentElement(\"afterbegin\", this.prevEl);\n            }\n        }\n        if (this.nextEl) {\n            if (this.page >= this.nbPages - 1) {\n                this.nextEl.remove();\n            } else {\n                this.nextEl.classList.remove(\"d-none\");\n                this.insert(this.nextEl, this.indicatorEl, \"beforeend\");\n            }\n        }\n    }\n\n    onSlidCarousel() {\n        if (this.liEls) {\n            const active = [...this.liEls].filter((el) => el.classList.contains(\"active\"));\n            const index = active.length ? [...this.liEls].indexOf(active[0]) : 0;\n            this.page = Math.floor(index / this.realNbPerPage);\n        }\n        this.hide();\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.gallery_slider\", GallerySlider);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { _t } from \"@web/core/l10n/translation\";\n\n// Note that Instagram can automatically detect the language of the user and\n// translate the embed.\n\nexport class InstagramPage extends Interaction {\n    static selector = \".s_instagram_page\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _iframe: () => this.iframeEl,\n    };\n    dynamicContent = {\n        // We have to setup the message listener before setting the src, because\n        // the iframe can send a message before this JS is fully loaded.\n        _window: { \"t-on-message\": this.onMessage },\n        _iframe: { \"t-att-height\": () => this.height },\n    };\n\n    setup() {\n        this.iframeEl = document.createElement(\"iframe\");\n        this.iframeEl.setAttribute(\"scrolling\", \"no\");\n        this.iframeEl.setAttribute(\"aria-label\", _t(\"Instagram\"));\n        this.iframeEl.classList.add(\"w-100\");\n        this.insert(this.iframeEl, this.el.querySelector(\".o_instagram_container\"));\n\n        // In the meantime Instagram doesn't send us a message with the height,\n        // we use a formula to estimate the height of the iframe (the formula\n        // has been found with a linear regression).\n        const iframeWidth = parseInt(getComputedStyle(this.iframeEl).width);\n        // The profile picture is smaller when width < 432px.\n        this.height = Math.ceil(0.659 * iframeWidth + (iframeWidth < 432 ? 156 : 203));\n    }\n\n    start() {\n        const src = `https://www.instagram.com/${this.el.dataset.instagramPage}/embed`;\n        this.services.website_cookies.manageIframeSrc(this.iframeEl, src);\n    }\n\n    /**\n     * Instagram sends us a message with the height of the iframe.\n     *\n     * @param {Event} ev\n     */\n    onMessage(ev) {\n        if (\n            ev.origin !== \"https://www.instagram.com\" ||\n            this.iframeEl.contentWindow !== ev.source\n        ) {\n            return;\n        }\n        const evDataJSON = JSON.parse(ev.data);\n        if (evDataJSON.type !== \"MEASURE\") {\n            return;\n        }\n        const height = parseInt(evDataJSON.details.height);\n        // Here we get the exact height of the iframe.\n        // Instagram can return a height of 0 before the real height.\n        if (height) {\n            this.height = height;\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.instagram_page\", InstagramPage);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { generateGMapLink, generateGMapIframe } from \"@website/js/utils\";\n\nexport class Map extends Interaction {\n    static selector = \".s_map\";\n\n    start() {\n        if (!this.el.querySelector(\".s_map_embedded\")) {\n            // The iframe is not found inside the snippet. This is probably due\n            // to the sanitization of a field during the save, like in a product\n            // description field. In such cases, reconstruct the iframe.\n            const dataset = this.el.dataset;\n            if (dataset.mapAddress) {\n                const iframeEl = generateGMapIframe();\n                this.el.querySelector(\".s_map_color_filter\").before(iframeEl);\n                this.services.website_cookies.manageIframeSrc(iframeEl, generateGMapLink(dataset));\n            }\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.map\", Map);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { markup } from \"@odoo/owl\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { getTemplate } from \"@web/core/templates\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\n\nexport class SearchBar extends Interaction {\n    static selector = \".o_searchbar_form\";\n    dynamicContent = {\n        _root: {\n            \"t-on-focusout\": this.debounced(this.onFocusOut, 100),\n            \"t-on-safarihack\": (ev) => (this.linkHasFocus = ev.detail.linkHasFocus),\n            \"t-att-class\": () => ({\n                dropdown: this.hasDropdown,\n                show: this.hasDropdown,\n            }),\n        },\n        \".search-query\": {\n            \"t-on-input\": this.debounced(this.onInput, 400),\n            \"t-on-keydown\": this.onKeydown,\n            \"t-on-search\": this.onSearch,\n        },\n    };\n    autocompleteMinWidth = 300;\n\n    setup() {\n        this.keepLast = new KeepLast();\n        this.inputEl = this.el.querySelector(\".search-query\");\n        this.menuEl = null;\n        this.searchType = this.inputEl.dataset.searchType;\n        const orderByEl = this.el.querySelector(\".o_search_order_by\");\n        const form = orderByEl.closest(\"form\");\n        this.order = orderByEl.value;\n        this.limit = parseInt(this.inputEl.dataset.limit) || 5;\n        this.wasEmpty = !this.inputEl.value;\n        this.linkHasFocus = false;\n        if (this.limit) {\n            this.inputEl.setAttribute(\"autocomplete\", \"off\");\n        }\n        const dataset = this.inputEl.dataset;\n        this.options = {\n            displayImage: dataset.displayImage,\n            displayDescription: dataset.displayDescription,\n            displayExtraLink: dataset.displayExtraLink,\n            displayDetail: dataset.displayDetail,\n            // Make it easy for customization to disable fuzzy matching on specific searchboxes\n            allowFuzzy: !dataset.noFuzzy,\n        };\n        for (const fieldEl of form.querySelectorAll(\"input[type='hidden']\")) {\n            this.options[fieldEl.name] = fieldEl.value;\n        }\n        const action =\n            form.getAttribute(\"action\") || window.location.pathname + window.location.search;\n        const [urlPath, urlParams] = action.split(\"?\");\n        if (urlParams) {\n            for (const keyValue of urlParams.split(\"&\")) {\n                const [key, value] = keyValue.split(\"=\");\n                if (value && key !== \"search\") {\n                    // Decode URI parameters: revert + to space then decodeURIComponent.\n                    this.options[decodeURIComponent(key.replace(/\\+/g, \"%20\"))] =\n                        decodeURIComponent(value.replace(/\\+/g, \"%20\"));\n                }\n            }\n        }\n        const pathParts = urlPath.split(\"/\");\n        for (const index in pathParts) {\n            const value = decodeURIComponent(pathParts[index]);\n            const indexNumber = parseInt(index);\n            if (indexNumber > 0 && /-[0-9]+$/.test(value)) {\n                // is sluggish\n                this.options[decodeURIComponent(pathParts[indexNumber - 1])] = value;\n            }\n        }\n    }\n\n    start() {\n        if (this.inputEl.dataset.noFuzzy) {\n            const noFuzzyEl = document.createElement(\"input\");\n            noFuzzyEl.setAttribute(\"type\", \"hidden\");\n            noFuzzyEl.setAttribute(\"name\", \"noFuzzy\");\n            noFuzzyEl.setAttribute(\"value\", \"true\");\n            this.insert(noFuzzyEl, this.inputEl);\n        }\n    }\n\n    destroy() {\n        this.render(null);\n    }\n\n    async fetch() {\n        const res = await rpc(\"/website/snippet/autocomplete\", {\n            search_type: this.searchType,\n            term: this.inputEl.value,\n            order: this.order,\n            limit: this.limit,\n            max_nb_chars: Math.round(\n                Math.max(this.autocompleteMinWidth, parseInt(this.el.clientWidth)) * 0.22\n            ),\n            options: this.options,\n        });\n        const fieldNames = this.getFieldsNames();\n        res.results.forEach((record) => {\n            for (const fieldName of fieldNames) {\n                if (record[fieldName]) {\n                    record[fieldName] = markup(record[fieldName]);\n                }\n            }\n        });\n        return res;\n    }\n\n    /**\n     * @param {Object} res\n     */\n    render(res) {\n        if (this.menuEl) {\n            this.services[\"public.interactions\"].stopInteractions(this.menuEl);\n        }\n        const prevMenuEl = this.menuEl;\n        if (res && this.limit) {\n            const results = res[\"results\"];\n            let template = \"website.s_searchbar.autocomplete\";\n            const candidate = template + \".\" + this.searchType;\n            if (getTemplate(candidate)) {\n                template = candidate;\n            }\n            this.menuEl = this.renderAt(\n                template,\n                {\n                    results: results,\n                    parts: res[\"parts\"],\n                    hasMoreResults: results.length < res[\"results_count\"],\n                    search: this.inputEl.value,\n                    fuzzySearch: res[\"fuzzy_search\"],\n                    widget: this.options,\n                },\n                this.el\n            )[0];\n        }\n        this.hasDropdown = !!res;\n        prevMenuEl?.remove();\n    }\n\n    getFieldsNames() {\n        return [\"description\", \"detail\", \"detail_extra\", \"detail_strike\", \"extra_link\", \"name\"];\n    }\n\n    async onInput() {\n        if (!this.limit) {\n            return;\n        }\n        if (this.searchType === \"all\" && !this.inputEl.value.trim().length) {\n            this.render();\n        } else {\n            const res = await this.keepLast.add(this.waitFor(this.fetch()));\n            this.render(res);\n        }\n    }\n\n    onFocusOut() {\n        if (\n            !this.linkHasFocus &&\n            document.activeElement?.closest(\".o_searchbar_form\") !== this.el\n        ) {\n            this.render();\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onKeydown(ev) {\n        switch (ev.key) {\n            case \"Escape\":\n                this.render();\n                break;\n            case \"ArrowUp\":\n            case \"ArrowDown\":\n                ev.preventDefault();\n                if (this.menuEl) {\n                    const focusableEls = [this.inputEl, ...this.menuEl.children];\n                    const focusedEl = document.activeElement;\n                    const currentIndex = focusableEls.indexOf(focusedEl) || 0;\n                    const delta = ev.key === \"ArrowUp\" ? focusableEls.length - 1 : 1;\n                    const nextIndex = (currentIndex + delta) % focusableEls.length;\n                    const nextFocusedEl = focusableEls[nextIndex];\n                    nextFocusedEl.focus();\n                }\n                break;\n            case \"Enter\":\n                this.limit = 0; // prevent autocomplete\n                break;\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onSearch(ev) {\n        if (this.inputEl.value) {\n            // actual search\n            this.limit = 0; // prevent autocomplete\n        } else {\n            // clear button clicked\n            this.render(); // remove existing suggestions\n            ev.preventDefault();\n        }\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.search_bar\", SearchBar);\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\n\nimport { isBrowserSafari } from \"@web/core/browser/feature_detection\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { verifyHttpsUrl } from \"@website/utils/misc\";\n\nexport class SearchBarResults extends Interaction {\n    static selector = \".o_searchbar_form .o_dropdown_menu\";\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _scrollingParent: () => this.scrollingParentEl,\n        _searchbar: () => this.searchBarEl,\n    };\n    dynamicContent = {\n        _root: {\n            \"t-att-style\": () => {\n                const bcr = this.searchBarEl.getBoundingClientRect();\n                return {\n                    position: \"absolute !important\",\n                    \"max-width\": `${bcr.width}px !important`,\n                    \"max-height\": `max(40vh, ${\n                        document.body.clientHeight - bcr.bottom - 16\n                    }px) !important`,\n                    \"min-width\": this.autocompleteMinWidth,\n                };\n            },\n            \"t-att-class\": () => ({\n                show: true,\n            }),\n            \"t-att-data-bs-popper\": () => (this.isDropup ? \"\" : undefined),\n        },\n        _searchbar: {\n            \"t-att-class\": () => ({\n                dropup: this.isDropup,\n            }),\n        },\n        _window: {\n            \"t-on-resize\": () => {}, // Re-apply _root:t-att-style.\n        },\n        _scrollingParent: {\n            \"t-on-scroll\": () => {}, // Re-apply _root:t-att-style.\n        },\n        \".dropdown-item\": {\n            \"t-on-mousedown\": this.onMousedown,\n            \"t-on-mouseup\": this.onMouseup,\n            \"t-on-keydown\": this.onKeydown,\n        },\n        \"button.extra_link\": {\n            \"t-on-click.prevent\": this.onExtraLinkClick,\n        },\n        \".s_searchbar_fuzzy_submit\": {\n            \"t-on-click.prevent\": (event) => {\n                this.inputEl.value = event.target.textContent;\n                const formEl = this.searchBarEl.querySelector(\".o_search_order_by\").closest(\"form\");\n                formEl.submit();\n            },\n        },\n    };\n    autocompleteMinWidth = 300;\n\n    setup() {\n        this.searchBarEl = this.el.closest(\".o_searchbar_form\");\n        this.inputEl = this.searchBarEl.querySelector(\".search-query\");\n        this.scrollingParentEl = null;\n\n        // Handle the case where the searchbar is in a mega menu by making\n        // it position:fixed and forcing its size. Note: this could be the\n        // default behavior or at least needed in more cases than the mega\n        // menu only (all scrolling parents). But as a stable fix, it was\n        // easier to fix that case only as a first step, especially since\n        // this cannot generically work on all scrolling parent.\n        const megaMenuEl = this.searchBarEl.closest(\".o_mega_menu\");\n        if (megaMenuEl) {\n            const navbarEl = this.searchBarEl.closest(\".navbar\");\n            const navbarTogglerEl = navbarEl ? navbarEl.querySelector(\".navbar-toggler\") : null;\n            if (navbarTogglerEl && navbarTogglerEl.clientWidth < 1) {\n                this.scrollingParentEl = megaMenuEl;\n            }\n        }\n\n        // Adjust the menu's position based on the scroll height.\n        this.isDropup = false;\n        if (this.el.getBoundingClientRect().bottom > document.documentElement.offsetHeight) {\n            // If the menu overflows below the page, we reduce its height.\n            this.el.style.overflowY = \"auto\";\n            // We then recheck if the menu still overflows below the page.\n            if (this.el.getBoundingClientRect().bottom > document.documentElement.offsetHeight) {\n                // If the menu still overflows below the viewport after its\n                // height has been reduced, we position it where most space is\n                // available\n                const searchPosition = this.searchBarEl.getBoundingClientRect();\n                this.isDropup =\n                    searchPosition.top >\n                    document.documentElement.offsetHeight - searchPosition.bottom;\n            }\n        }\n    }\n\n    onMousedown() {\n        // On Safari, links and buttons are not focusable by default. We need\n        // to get around that behavior to avoid onFocusOut() from triggering\n        // render(), as this would prevent the click from working.\n        if (isBrowserSafari) {\n            this.searchBarEl.dispatchEvent(\n                new CustomEvent(\"safarihack\", { detail: { linkHasFocus: true } })\n            );\n        }\n    }\n\n    onMouseup() {\n        // See comment in onMousedown.\n        if (isBrowserSafari) {\n            this.searchBarEl.dispatchEvent(\n                new CustomEvent(\"safarihack\", { detail: { linkHasFocus: false } })\n            );\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onKeydown(ev) {\n        switch (ev.key) {\n            case \"ArrowUp\":\n            case \"ArrowDown\": {\n                ev.preventDefault();\n                const focusableEls = [this.inputEl, ...this.el.children];\n                const focusedEl = document.activeElement;\n                const currentIndex = focusableEls.indexOf(focusedEl) || 0;\n                const delta = ev.key === \"ArrowUp\" ? focusableEls.length - 1 : 1;\n                const nextIndex = (currentIndex + delta) % focusableEls.length;\n                const nextFocusedEl = focusableEls[nextIndex];\n                nextFocusedEl.focus();\n                break;\n            }\n        }\n    }\n\n    /**\n     * @param {PointerEvent} ev\n     */\n    onExtraLinkClick(ev) {\n        browser.location.href = verifyHttpsUrl(ev.currentTarget.dataset.target);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.search_bar_results\", SearchBarResults);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class Share extends Interaction {\n    static selector = \".s_share, .oe_share\";\n    dynamicContent = {\n        a: { \"t-on-click\": this.onClick },\n    };\n\n    /**\n     * Everything is done on click here (even changing the href) as the URL we\n     * want to share may be updated during the page use (like when updating\n     * variant on a product page then clicking on a share link).\n     *\n     * @param {Event} ev\n     */\n    onClick(ev) {\n        const urlParams = [\"u\", \"url\", \"body\"];\n        const titleParams = [\"title\", \"text\", \"subject\", \"description\"];\n        const mediaParams = [\"media\"];\n        const aEl = ev.currentTarget;\n\n        // We don't modify the original URL in case the user clicks again on the\n        // sharer later.\n        const modifiedUrl = new URL(aEl.href);\n\n        // Try and support old use of share snippet as a social link snippet:\n        // if the URL does not look like a sharer, then do nothing. This\n        // obviously won't cover all cases (people may have added URL that look\n        // like sharer but are not but in that case, it was probably already\n        // broken before).\n        if (\n            ![...urlParams, ...titleParams, ...mediaParams].some((param) =>\n                modifiedUrl.searchParams.has(param)\n            )\n        ) {\n            return;\n        }\n\n        ev.preventDefault();\n        ev.stopPropagation();\n\n        // We don't need to encode the URL as searchParams.set does it for us.\n        const currentUrl = window.location.href;\n\n        const urlParamFound = urlParams.find((param) => modifiedUrl.searchParams.has(param));\n        if (urlParamFound) {\n            modifiedUrl.searchParams.set(urlParamFound, currentUrl);\n        }\n\n        const titleParamFound = titleParams.find((param) => modifiedUrl.searchParams.has(param));\n        if (titleParamFound) {\n            // We don't need to encode the title as searchParams.set does it.\n            const currentTitle = document.title;\n            if (aEl.classList.contains(\"s_share_whatsapp\")) {\n                // WhatsApp does not support the \"url\" GET parameter.\n                // Instead we need to include the url within the passed \"text\"\n                // parameter, merging everything together, e.g of output:\n                // https://wa.me/?text=%20OpenWood%20Collection%20Online%20Reveal%20%7C%20My%20Website%20http%3A%2F%2Flocalhost%3A8888%2Fevent%2Fopenwood-collection-online-reveal-2021-06-21-2021-06-23-8%2Fregister\n                // For more details, see https://faq.whatsapp.com/general/chats/how-to-use-click-to-chat/\n                modifiedUrl.searchParams.set(titleParamFound, `${currentTitle} ${currentUrl}`);\n            } else {\n                // The built-in `URLSearchParams.set()` method encodes spaces\n                // as \"+\" characters, which are not properly parsed as spaces\n                // by email clients, so we can't use it here.\n                modifiedUrl.search = modifiedUrl.search.replace(\n                    encodeURIComponent(\"{title}\"),\n                    encodeURIComponent(currentTitle)\n                );\n            }\n        }\n\n        const mediaParamFound = mediaParams.find((param) => modifiedUrl.searchParams.has(param));\n        if (mediaParamFound) {\n            const ogImageEl = document.querySelector(\"meta[property='og:image']\");\n            // Some pages (/profile/user/ID) don't have an image to share.\n            if (ogImageEl) {\n                // We don't need to encode the media as searchParams does it.\n                const media = ogImageEl.content;\n                modifiedUrl.searchParams.set(mediaParamFound, media);\n            } else {\n                modifiedUrl.searchParams.delete(mediaParamFound);\n            }\n        }\n\n        window.open(\n            modifiedUrl.toString(),\n            aEl.target,\n            \"menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=550,width=600\"\n        );\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.share\", Share);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { patch } from \"@web/core/utils/patch\";\nimport { closestScrollableY, isScrollableY } from \"@web/core/utils/scrolling\";\nimport { isVisible } from \"@web/core/utils/ui\";\nimport { AnchorSlide } from \"@website/interactions/anchor_slide\";\n\nconst getSelector = (element) => {\n    const hrefAttr = element.getAttribute(\"href\");\n    if (!hrefAttr?.startsWith(\"#\")) {\n        return null;\n    }\n    return hrefAttr !== \"#\" ? hrefAttr.trim() : null;\n};\n\nconst parents = (element, selector) => {\n    const parents = [];\n    let ancestor = element.parentElement.closest(selector);\n    while (ancestor) {\n        parents.push(ancestor);\n        ancestor = ancestor.parentElement.closest(selector);\n    }\n    return parents;\n};\n\nconst prev = (element, selector) => {\n    let previous = element.previousElementSibling;\n    while (previous) {\n        if (previous.matches(selector)) {\n            return [previous];\n        }\n        previous = previous.previousElementSibling;\n    }\n    return [];\n};\n\nexport class TableOfContent extends Interaction {\n    static selector = \"section .s_table_of_content_navbar_sticky\";\n    dynamicContent = {\n        _root: {\n            \"t-att-style\": () => ({\n                top: this.isHorizontal ? `${this.position}px` : undefined,\n            }),\n        },\n        \".s_table_of_content_navbar\": {\n            \"t-att-style\": () => ({\n                top: this.isHorizontal ? undefined : `${this.position}px`,\n                maxHeight: this.isHorizontal ? undefined : `calc(100vh - ${this.position + 40}px)`,\n            }),\n        },\n    };\n\n    setup() {\n        this.position = 20;\n        this.isHorizontal = this.el.classList.contains(\"s_table_of_content_horizontal_navbar\");\n\n        this.scrollBound = this.process.bind(this);\n        this.offsets = [];\n        this.targets = [];\n        this.activeTarget = null;\n        this.scrollHeight = 0;\n        this.offset = 0;\n\n        this.scrollElement =\n            closestScrollableY(this.el.closest(\".s_table_of_content\")) ||\n            this.el.ownerDocument.scrollingElement;\n        this.scrollTarget = isScrollableY(this.scrollElement)\n            ? this.scrollElement\n            : this.scrollElement.ownerDocument.defaultView;\n        this.tocElement = this.el.querySelector(\".s_table_of_content_navbar\");\n        this.previousPosition = -1;\n    }\n\n    start() {\n        this.updateTableOfContentNavbarPosition();\n        this.registerCleanup(\n            this.services.website_menus.registerCallback(\n                this.updateTableOfContentNavbarPosition.bind(this)\n            )\n        );\n\n        this.addListener(this.scrollTarget, \"scroll\", this.scrollBound);\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    updateTableOfContentNavbarPosition() {\n        if (!this.el.querySelector(\"a.table_of_content_link\")) {\n            // Do not start the scrollspy if the TOC is empty.\n            return;\n        }\n\n        let position = this.isHorizontal ? 0 : 20;\n        for (const el of this.el.ownerDocument.querySelectorAll(\".o_top_fixed_element\")) {\n            position += el.getBoundingClientRect().bottom;\n        }\n\n        this.position = position;\n        position += this.isHorizontal ? this.el.offsetHeight : 0;\n\n        if (this.previousPosition !== position) {\n            this.offset = position + 100;\n            this.refresh();\n            this.process();\n            this.previousPosition = position;\n        }\n        this.updateContent();\n    }\n\n    getScrollHeight() {\n        return (\n            this.scrollElement.scrollHeight ||\n            Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)\n        );\n    }\n\n    refresh() {\n        this.offsets = [];\n        this.targets = [];\n        this.scrollHeight = this.getScrollHeight();\n        const targets = [\n            ...this.tocElement.querySelectorAll(\".nav-link, .list-group-item, .dropdown-item\"),\n        ];\n        targets\n            .map((element) => {\n                const targetSelector = getSelector(element);\n                const target = targetSelector ? document.querySelector(targetSelector) : null;\n                if (target) {\n                    const targetBCR = target.getBoundingClientRect();\n\n                    if (targetBCR.width || targetBCR.height) {\n                        return [targetBCR.top, targetSelector];\n                    }\n                }\n                return null;\n            })\n            .filter((item) => item)\n            .sort((a, b) => a[0] - b[0])\n            .forEach((item) => {\n                this.offsets.push(item[0]);\n                this.targets.push(item[1]);\n            });\n        const baseScrollTop = this.scrollElement.scrollTop;\n        for (let i = 0; i < this.offsets.length; i++) {\n            this.offsets[i] += baseScrollTop;\n        }\n    }\n\n    /**\n     * @param {string} target\n     */\n    activate(target) {\n        const element = document.querySelector(`[href=\"${target}\"]`);\n        if (!element || !isVisible(element)) {\n            return;\n        }\n        this.activeTarget = target;\n        this.clear();\n        const queries = \".nav-link, .list-group-item, .dropdown-item\"\n            .split(\",\")\n            .map((selector) => `${selector}[href=\"${target}\"]`);\n        const link = this.tocElement.querySelector(queries.join(\",\"));\n        link.classList.add(\"active\");\n        if (link.classList.contains(\"dropdown-item\")) {\n            link.closest(\".dropdown\").querySelector(\".dropdown-toggle\").classList.add(\"active\");\n        } else {\n            const listGroupEls = parents(link, \".nav, .list-group\");\n            for (const listGroupEl of listGroupEls) {\n                // Set triggered links parents as active\n                // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n                const itemEls = prev(listGroupEl, \".nav-link, .list-group-item\");\n                for (const itemEl of itemEls) {\n                    itemEl.classList.add(\"active\");\n                }\n                // Handle special case when .nav-link is inside .nav-item\n                const navItemEls = prev(listGroupEl, \".nav-item\");\n                for (const navItemEl of navItemEls) {\n                    for (const childEl of navItemEl.children) {\n                        if (childEl.matches(\".nav-link\")) {\n                            childEl.classList.add(\"active\");\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    clear() {\n        const itemEls = this.tocElement.querySelectorAll(\n            \".nav-link, .list-group-item, .dropdown-item\"\n        );\n        for (const itemEl of itemEls) {\n            itemEl.classList.remove(\"active\");\n        }\n    }\n\n    process() {\n        const scrollTop = this.scrollElement.scrollTop + this.offset;\n        const scrollHeight = this.getScrollHeight();\n        const maxScroll =\n            this.offset + scrollHeight - this.scrollElement.getBoundingClientRect().height;\n        if (this.scrollHeight !== scrollHeight) {\n            this.refresh();\n        }\n        if (scrollTop >= maxScroll) {\n            const target = this.targets[this.targets.length - 1];\n            if (this.activeTarget !== target) {\n                this.activate(target);\n            }\n            return;\n        }\n        if (this.activeTarget && scrollTop < this.offsets[0] && this.offsets[0] > 0) {\n            this.activeTarget = null;\n            this.clear();\n        } else {\n            for (let i = this.offsets.length; i--; ) {\n                const isActiveTarget =\n                    this.activeTarget !== this.targets[i] &&\n                    scrollTop >= this.offsets[i] &&\n                    (typeof this.offsets[i + 1] === \"undefined\" || scrollTop < this.offsets[i + 1]);\n\n                if (isActiveTarget) {\n                    this.activate(this.targets[i]);\n                }\n            }\n        }\n        if (this.activeTarget === null) {\n            this.activate(this.targets[0]);\n        }\n    }\n}\n\npatch(AnchorSlide.prototype, {\n    /**\n     * Overridden to add the height of the horizontal sticky navbar at the scroll value\n     * when the link is from the table of content navbar\n     *\n     * @override\n     */\n    computeExtraOffset() {\n        let extraOffset = super.computeExtraOffset(...arguments);\n        if (this.el.classList.contains(\"table_of_content_link\")) {\n            const tableOfContentNavbarEl = this.el.closest(\n                \".s_table_of_content_navbar_sticky.s_table_of_content_horizontal_navbar\"\n            );\n            if (tableOfContentNavbarEl) {\n                extraOffset += tableOfContentNavbarEl.getBoundingClientRect().height;\n            }\n        }\n        return extraOffset;\n    },\n});\n\nregistry.category(\"public.interactions\").add(\"website.table_of_content\", TableOfContent);\n", "import { scrollTo } from \"@html_builder/utils/scrolling\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { ReCaptcha } from \"@google_recaptcha/js/recaptcha\";\nimport { localization } from \"@web/core/l10n/localization\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { post } from \"@web/core/network/http_service\";\nimport { user } from \"@web/core/user\";\nimport { delay } from \"@web/core/utils/concurrency\";\nimport { session } from \"@web/session\";\nimport {\n    formatDate,\n    formatDateTime,\n    parseDate,\n    parseDateTime,\n    serializeDate,\n    serializeDateTime,\n} from \"@web/core/l10n/dates\";\nimport wUtils from \"@website/js/utils\";\n\nconst { DateTime } = luxon;\n\nexport class Form extends Interaction {\n    static selector = \".s_website_form form, form.s_website_form\"; // !compatibility\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _endMessage: () => this.el.parentNode.querySelector(\".s_website_form_end_message\"),\n    };\n    dynamicContent = {\n        \".s_website_form_send, .o_website_form_send\": {\n            \"t-on-click.prevent\": this.locked(this.send, true),\n        }, // !compatibility\n        _root: {\n            \"t-on-submit.prevent\": this.locked(this.send, true),\n            \"t-att-class\": () => ({\n                \"d-none\": this.isHidden,\n            }),\n        },\n        _endMessage: {\n            \"t-att-class\": () => ({\n                \"d-none\": !this.isHidden,\n            }),\n        },\n        \"input[type=file]\": { \"t-on-change\": this.changeFile },\n        \"input.o_add_files_button\": { \"t-on-click\": this.clickAddFilesButton },\n        \".s_website_form_field[data-type=binary]\": { \"t-on-click\": this.clickFileDelete }, // delegate on \".o_file_delete\"\n        \".s_website_form_field\": {\n            \"t-on-input\": this.debounced(this.onFieldInput, 300),\n            \"t-att-class\": (el) => ({ \"d-none\": !this.isFieldVisible(el) }),\n        },\n        // Do not disable inputs that are required for the model.\n        \".s_website_form_field:not(.s_website_form_model_required) .s_website_form_input\": {\n            \"t-att-disabled\": (el) => !this.isInputVisible(el) || undefined,\n        },\n        \".s_website_form_datetime, .o_website_form_datetime, .s_website_form_date, .o_website_form_date\":\n            {\n                \"t-att-class\": () => ({\n                    s_website_form_datepicker_initialized: this.datepickerInitialized,\n                }),\n            },\n    };\n\n    setup() {\n        this.isHidden = false;\n        this.datepickerInitialized = false;\n        this.recaptcha = new ReCaptcha();\n        this.initialValues = new Map();\n        this.disabledStates = new Map();\n        this.visibilityFunctionByFieldEl = new Map();\n        this.visibilityFunctionByFieldName = new Map();\n        this.inputEls = this.el.querySelectorAll(\n            \".s_website_form_field.s_website_form_field_hidden_if .s_website_form_input\"\n        );\n        this.dateFieldEls = this.el.querySelectorAll(\n            \".s_website_form_datetime, .o_website_form_datetime, .s_website_form_date, .o_website_form_date\"\n        );\n        this.disableDateTimePickers = [];\n        this.preFillValues = {};\n        this.lastFormData = this.getFormDataIncludingDisabledFields(this.el);\n    }\n\n    async willStart() {\n        if (!this.el.classList.contains(\"s_website_form_no_recaptcha\")) {\n            this.recaptchaLoaded = true;\n            await this.recaptcha.loadLibs();\n        }\n        // fetch user data (required by fill-with behavior)\n        if (user.userId) {\n            this.preFillValues =\n                (\n                    await this.services.orm.read(\n                        \"res.users\",\n                        [user.userId],\n                        this.getUserPreFillFields()\n                    )\n                )[0] || {};\n        }\n        // Reset the form first, as it is still filled when coming back\n        // after a redirect.\n        this.resetForm();\n\n        // Prepare visibility data and update field visibilities\n        const visibilityFunctionsByFieldName = new Map();\n        for (const fieldEl of this.el.querySelectorAll(\"[data-visibility-dependency]\")) {\n            const inputName = fieldEl.querySelector(\".s_website_form_input\").name;\n            if (!visibilityFunctionsByFieldName.has(inputName)) {\n                visibilityFunctionsByFieldName.set(inputName, []);\n            }\n            const func = this.buildVisibilityFunction(fieldEl);\n            visibilityFunctionsByFieldName.get(inputName).push(func);\n            this.visibilityFunctionByFieldEl.set(fieldEl, func);\n        }\n        for (const [name, funcs] of visibilityFunctionsByFieldName.entries()) {\n            this.visibilityFunctionByFieldName.set(name, () => funcs.some((func) => func()));\n        }\n    }\n\n    start() {\n        this.prepareDateFields();\n        this.prefillValues();\n\n        // Visibility might need to be adapted according to pre-filled values.\n        this.lastFormData = this.getFormDataIncludingDisabledFields(this.el);\n        this.updateContent();\n\n        if (session.geoip_phone_code) {\n            this.el.querySelectorAll(`input[type=\"tel\"]`).forEach((telField) => {\n                if (!telField.value) {\n                    telField.value = \"+\" + session.geoip_phone_code;\n                }\n            });\n        }\n        // Check disabled states\n        for (const inputEl of this.inputEls) {\n            this.disabledStates[inputEl] = inputEl.disabled;\n        }\n\n        // Add the files zones where the file blocks will be displayed.\n        this.el.querySelectorAll(\"input[type=file]\").forEach((inputEl) => {\n            const filesZoneEl = document.createElement(\"DIV\");\n            filesZoneEl.classList.add(\"o_files_zone\", \"row\", \"gx-1\");\n            inputEl.parentNode.insertBefore(filesZoneEl, inputEl);\n        });\n    }\n\n    destroy() {\n        // TODO Find out which event this is about.\n        // this.$el.find(\"button\").off(\"click\");\n\n        // Empty inputs\n        this.resetForm();\n\n        // Apply default values\n        this.el\n            .querySelectorAll(`input[type=\"text\"], input[type=\"email\"], input[type=\"number\"]`)\n            .forEach((el) => {\n                let value = el.getAttribute(\"value\");\n                if (value) {\n                    if (el.classList.contains(\"datetimepicker-input\")) {\n                        const format =\n                            el.closest(\".s_website_form_field\").dataset.type === \"date\"\n                                ? formatDate\n                                : formatDateTime;\n                        value = format(DateTime.fromSeconds(parseInt(value)));\n                    }\n                    el.value = value;\n                }\n            });\n        this.el.querySelectorAll(\"textarea\").forEach((el) => (el.value = el.textContent));\n\n        // Remove saving of the error colors\n        for (const errorEl of this.el.querySelectorAll(\".o_has_error\")) {\n            errorEl.classList.remove(\"o_has_error\");\n            for (const el of errorEl.querySelectorAll(\".form-control, .form-select\")) {\n                el.classList.remove(\"is-invalid\");\n            }\n        }\n\n        // Remove the status message\n        this.el.querySelector(\"#s_website_form_result, #o_website_form_result\")?.replaceChildren(); // !compatibility\n\n        // Restore disabled attribute\n        for (const inputEl of this.inputEls) {\n            inputEl.disabled = !!this.disabledStates.get(inputEl);\n        }\n\n        // All 'hidden if' fields start with d-none\n        this.el\n            .querySelectorAll(\".s_website_form_field_hidden_if:not(.d-none)\")\n            .forEach((el) => el.classList.add(\"d-none\"));\n\n        // Prevent \"data-for\" values removal on destroy, they are still used\n        // in edit mode to keep the form linked to its predefined server\n        // values (e.g., the default `job_id` value on the application form\n        // for a given job).\n        const dataForValues = wUtils.getParsedDataFor(this.el.id, document) || {};\n        const initialValuesToReset = new Map(\n            [...this.initialValues.entries()].filter(\n                ([input]) => !dataForValues[input.name] || input.name === \"email_to\"\n            )\n        );\n\n        // Reset the initial default values.\n        for (const [fieldEl, initialValue] of initialValuesToReset.entries()) {\n            if (initialValue) {\n                fieldEl.setAttribute(\"value\", initialValue);\n            } else {\n                fieldEl.removeAttribute(\"value\");\n            }\n        }\n\n        for (const disableDateTimePicker of this.disableDateTimePickers) {\n            disableDateTimePicker();\n        }\n    }\n\n    prepareDateFields() {\n        for (const fieldEl of this.dateFieldEls) {\n            const inputEl = fieldEl.querySelector(\"input\");\n            const defaultValue = inputEl.getAttribute(\"value\");\n            this.disableDateTimePickers.push(\n                this.services.datetime_picker\n                    .create({\n                        target: inputEl,\n                        onChange: () =>\n                            inputEl.dispatchEvent(new Event(\"input\", { bubbles: true })),\n                        pickerProps: {\n                            type: fieldEl.matches(\".s_website_form_date, .o_website_form_date\")\n                                ? \"date\"\n                                : \"datetime\",\n                            value: defaultValue && DateTime.fromSeconds(parseInt(defaultValue)),\n                        },\n                    })\n                    .enable()\n            );\n            // Disable virtual keyboard to fix popover display issues on small\n            // screens\n            inputEl.setAttribute(\"inputmode\", \"none\");\n        }\n        this.datepickerInitialized = true;\n    }\n\n    prefillValues() {\n        // Display form values from tag having data-for attribute\n        // It's necessary to handle field values generated on server-side\n        // Because, using t-att- inside form make it non-editable\n        // Data-fill-with attribute is given during registry and is used by\n        // to know which user data should be used to prfill fields.\n        let dataForValues = wUtils.getParsedDataFor(this.el.id, document);\n        // On the \"edit_translations\" mode, a <span/> with a translated term\n        // will replace the attribute value, leading to some inconsistencies\n        // (setting again the <span> on the attributes after the editor's\n        // cleanup, setting wrong values on the attributes after translating\n        // default values...)\n        if (dataForValues || Object.keys(this.preFillValues).length) {\n            dataForValues = dataForValues || {};\n            const fieldNames = [...this.el.querySelectorAll(\"[name]\")]\n                .filter((el) => ![\"submit\", \"button\", \"image\", \"reset\", \"file\"].includes(el.type))\n                .map((el) => el.name);\n\n            // All types of inputs do not have a value property (eg:hidden),\n            // for these inputs any function that is supposed to put a value\n            // property actually puts a HTML value attribute. Because of\n            // this, we have to clean up these values at destroy or else the\n            // data loaded here could become default values. We could set\n            // the values to submit() for these fields but this could break\n            // customizations that use the current behavior as a feature.\n            for (const name of fieldNames) {\n                const fieldEl = this.el.querySelector(`[name=\"${CSS.escape(name)}\"]`);\n\n                // In general, we want the data-for and prefill values to\n                // take priority over set default values. The 'email_to'\n                // field is however treated as an exception at the moment\n                // so that values set by users are always used.\n                if (\n                    name === \"email_to\" &&\n                    fieldEl.value &&\n                    // The following value is the default value that\n                    // is set if the form is edited in any way. (see the\n                    // @website/js/form_editor_registry module in editor\n                    // assets bundle).\n                    // TODO that value should probably never be forced\n                    // unless explicitely manipulated by the user or on\n                    // custom form addition but that seems risky to\n                    // change as a stable fix.\n                    fieldEl.value !== \"info@yourcompany.example.com\"\n                ) {\n                    continue;\n                }\n\n                let newValue;\n                if (dataForValues && dataForValues[name]) {\n                    newValue = dataForValues[name];\n                } else if (this.preFillValues[fieldEl.dataset.fillWith]) {\n                    newValue = this.preFillValues[fieldEl.dataset.fillWith];\n                }\n                if (newValue) {\n                    this.initialValues.set(fieldEl, fieldEl.getAttribute(\"value\"));\n                    fieldEl.value = newValue;\n                }\n            }\n        }\n    }\n\n    async send() {\n        this.el.querySelector(\"#s_website_form_result, #o_website_form_result\")?.replaceChildren(); // !compatibility\n        this.removeErrorMessages();\n        if (!this.checkErrorFields({})) {\n            this.updateStatus(\"error\", _t(\"Please fill in the form correctly.\"));\n            return false;\n        }\n\n        // Prepare form inputs\n        // Set a placeholder name to input fields without\n        // a label to allow FormData to function correctly\n        for (const [i, inputEl] of this.el\n            .querySelectorAll(\".s_website_form_input:is(:not([name]), [name=''])\")\n            .entries()) {\n            inputEl.setAttribute(\"name\", \"unknown_field_\" + (i + 1));\n        }\n\n        const formFields = [];\n        new FormData(this.el).forEach((value, key) => {\n            const inputElement = this.el.querySelector(`[name=\"${CSS.escape(key)}\"]`);\n            if (inputElement && inputElement.type !== \"file\") {\n                formFields.push({ name: key, value: value });\n            }\n        });\n        let outerIndex = 0;\n        for (const inputEl of this.el.querySelectorAll(\"input[type=file]:not([disabled])\")) {\n            let index = 0;\n            for (const file of inputEl.files) {\n                // Index field name as ajax won't accept arrays of files\n                // when aggregating multiple files into a single field value\n                formFields.push({\n                    name: `${inputEl.name}[${outerIndex}][${index}]`,\n                    value: file,\n                });\n                index++;\n            }\n            outerIndex++;\n        }\n\n        // Serialize form inputs into a single object\n        // Aggregate multiple values into arrays\n        const formValues = {};\n        formFields.forEach((input) => {\n            if (input.name in formValues) {\n                // If a value already exists for this field,\n                // we are facing a x2many field, so we store\n                // the values in an array.\n                if (Array.isArray(formValues[input.name])) {\n                    formValues[input.name].push(input.value);\n                } else {\n                    formValues[input.name] = [formValues[input.name], input.value];\n                }\n            } else {\n                if (input.value !== \"\") {\n                    formValues[input.name] = input.value;\n                }\n            }\n        });\n\n        // force server date format usage for existing fields\n        for (const fieldEl of this.el.querySelectorAll(\n            \".s_website_form_field:not(.s_website_form_custom)\"\n        )) {\n            for (const dateEl of fieldEl.querySelectorAll(\n                \".s_website_form_date, .s_website_form_datetime\"\n            )) {\n                const inputEl = dateEl.querySelector(\"input\");\n                const { value } = inputEl;\n                if (!value) {\n                    continue;\n                }\n\n                formValues[inputEl.getAttribute(\"name\")] = dateEl.matches(\".s_website_form_date\")\n                    ? serializeDate(parseDate(value))\n                    : serializeDateTime(parseDateTime(value));\n            }\n        }\n\n        if (this.recaptchaLoaded) {\n            const tokenObj = await this.waitFor(this.recaptcha.getToken(\"website_form\"));\n            if (tokenObj.token) {\n                formValues[\"recaptcha_token_response\"] = tokenObj.token;\n            } else if (tokenObj.error) {\n                this.updateStatus(\"error\", tokenObj.error);\n                return false;\n            }\n        }\n\n        if (odoo.csrf_token) {\n            formValues.csrf_token = odoo.csrf_token;\n        }\n\n        const formData = new FormData();\n        for (const [key, value] of Object.entries(formValues)) {\n            formData.append(key, value);\n        }\n\n        // Post form and handle result\n        return post(\n            this.el.getAttribute(\"action\") +\n                (this.el.dataset.force_action || this.el.dataset.model_name),\n            formData\n        )\n            .then(async (resultData) => {\n                if (!resultData.id) {\n                    // Failure, the server didn't return the created record ID\n                    this.updateStatus(\"error\", resultData.error ? resultData.error : false);\n                    if (resultData.error_fields) {\n                        // If the server return a list of bad fields, show these fields for users\n                        this.checkErrorFields(resultData.error_fields);\n                    }\n                } else {\n                    // Success, redirect or update status\n                    let successMode = this.el.dataset.successMode;\n                    let successPage = this.el.dataset.successPage;\n                    if (!successMode) {\n                        successPage = this.el.dataset.success_page; // !compatibility\n                        successMode = successPage ? \"redirect\" : \"nothing\";\n                    }\n                    switch (successMode) {\n                        case \"redirect\": {\n                            let hashIndex = successPage.indexOf(\"#\");\n                            if (hashIndex > 0) {\n                                // URL containing an anchor detected: extract\n                                // the anchor from the URL if the URL is the\n                                // same as the current page URL so we can scroll\n                                // directly to the element (if found) later\n                                // instead of redirecting.\n                                // Note that both currentUrlPath and successPage\n                                // can exist with or without a trailing slash\n                                // before the hash (e.g. \"domain.com#footer\" or\n                                // \"domain.com/#footer\"). Therefore, if they are\n                                // not present, we add them to be able to\n                                // compare the two variables correctly.\n                                let currentUrlPath = window.location.pathname;\n                                if (!currentUrlPath.endsWith(\"/\")) {\n                                    currentUrlPath = currentUrlPath + \"/\";\n                                }\n                                if (!successPage.includes(\"/#\")) {\n                                    successPage = successPage.replace(\"#\", \"/#\");\n                                    hashIndex++;\n                                }\n                                if (\n                                    [successPage, \"/\" + session.lang_url_code + successPage].some(\n                                        (link) => link.startsWith(currentUrlPath + \"#\")\n                                    )\n                                ) {\n                                    successPage = successPage.substring(hashIndex);\n                                }\n                            }\n                            if (successPage.charAt(0) === \"#\") {\n                                const successAnchorEl = document.getElementById(\n                                    successPage.substring(1)\n                                );\n                                if (successAnchorEl) {\n                                    // Check if the target of the link is a modal.\n                                    if (successAnchorEl.classList.contains(\"modal\")) {\n                                        // Trigger a \"hashChange\" event to\n                                        // notify the popup widget to show the\n                                        // popup.\n                                        window.location.href = successPage;\n                                    } else {\n                                        await this.waitFor(\n                                            scrollTo(successAnchorEl, {\n                                                duration: 500,\n                                                extraOffset: 0,\n                                            })\n                                        );\n                                    }\n                                }\n                                break;\n                            }\n                            window.location.href = successPage;\n                            return;\n                        }\n                        case \"message\": {\n                            // Prevent double-clicking on the send button and\n                            // add a upload loading effect (delay before success\n                            // message)\n                            await this.waitFor(delay(400));\n\n                            this.isHidden = true;\n                            break;\n                        }\n                        default: {\n                            // Prevent double-clicking on the send button and\n                            // add a upload loading effect (delay before success\n                            // message)\n                            await this.waitFor(delay(400));\n\n                            this.updateStatus(\"success\");\n                            break;\n                        }\n                    }\n\n                    this.resetForm();\n                }\n            })\n            .catch((error) => {\n                this.updateStatus(\n                    \"error\",\n                    error.message && error.message === \"Content too large\"\n                        ? _t(\"Uploaded file is too large.\")\n                        : \"\"\n                );\n            });\n    }\n\n    /**\n     * Resets a form.\n     */\n    resetForm() {\n        this.el.reset();\n\n        // Remove previous error messages.\n        this.removeErrorMessages();\n        // For file inputs, remove the files zone, restore the file input\n        // and remove the files list.\n        this.el.querySelectorAll(\"input[type=file]\").forEach((inputEl) => {\n            const fieldEl = inputEl.closest(\".s_website_form_field\");\n            fieldEl.querySelectorAll(\".o_files_zone\").forEach((el) => el.remove());\n            fieldEl.querySelectorAll(\".o_add_files_button\").forEach((el) => el.remove());\n            inputEl.classList.remove(\"d-none\");\n            delete inputEl.fileList;\n        });\n    }\n\n    checkErrorFields(errorFields) {\n        let formValid = true;\n        // Loop on all fields\n        for (const fieldEl of this.el.querySelectorAll(\".form-field, .s_website_form_field\")) {\n            // !compatibility\n            // FIXME that seems broken, \"for\" does not contain the field\n            // but this is used to retrieve errors sent from the server...\n            // need more investigation.\n            const fieldName = fieldEl.querySelector(\".col-form-label\")?.getAttribute(\"for\");\n\n            // Validate inputs for this field\n            const inputEls = [\n                ...fieldEl.querySelectorAll(\n                    \".s_website_form_input:not(#editable_select), .o_website_form_input:not(#editable_select)\"\n                ),\n            ]; // !compatibility\n            const invalidInputs = inputEls.filter((inputEl) => {\n                // Special check for multiple required checkbox for same\n                // field as it seems checkValidity forces every required\n                // checkbox to be checked, instead of looking at other\n                // checkboxes with the same name and only requiring one\n                // of them to be valid.\n                if (inputEl.required && inputEl.type === \"checkbox\") {\n                    // Considering we are currently processing a single\n                    // field, we can assume that all checkboxes in the\n                    // inputs variable have the same name\n                    // TODO should be improved: probably do not need to\n                    // filter neither on required, nor on checkbox and\n                    // checking the validity of the group of checkbox is\n                    // currently done for each checkbox of that group...\n                    const checkboxes = inputEls.filter(\n                        (el) => el.required && el.type === \"checkbox\"\n                    );\n                    return !checkboxes.some((checkbox) => checkbox.checkValidity());\n\n                    // Special cases for dates and datetimes\n                    // FIXME this seems like dead code, the inputs do not use\n                    // those classes, their parent does (but it seemed to work\n                    // at some point given that https://github.com/odoo/odoo/commit/75e03c0f7692a112e1b0fa33267f4939363f3871\n                    // was made)... need more investigation (if restored,\n                    // consider checking the date inputs are not disabled before\n                    // saying they are invalid (see checkValidity used here))\n                } else if (inputEl.matches(\".s_website_form_date, .o_website_form_date\")) {\n                    // !compatibility\n                    const date = parseDate(inputEl.value);\n                    if (!date || !date.isValid) {\n                        return true;\n                    }\n                } else if (inputEl.matches(\".s_website_form_datetime, .o_website_form_datetime\")) {\n                    // !compatibility\n                    const date = parseDateTime(inputEl.value);\n                    if (!date || !date.isValid) {\n                        return true;\n                    }\n                } else if (inputEl.type === \"file\" && !this.isFileInputValid(inputEl)) {\n                    return true;\n                } else if (this.requirementFunction(fieldEl) === false) {\n                    this.updateStatusInline(fieldEl.dataset.errorMessage, inputEl);\n                    return true;\n                }\n\n                // Note that checkValidity also takes care of the case where\n                // the input is disabled, in which case, it is considered\n                // valid (as the data will not be sent anyway).\n                // This takes care of conditionally-hidden fields (whose\n                // inputs are disabled while they are hidden) which should\n                // not require validation while they are hidden. Indeed,\n                // their purpose is to be able to enter additional data when\n                // some condition is fulfilled. If such a field is required,\n                // it is only required when visible for example.\n                return !inputEl.checkValidity();\n            });\n\n            // Update field color if invalid or erroneous\n            const controlEls = fieldEl.querySelectorAll(\n                \".form-control, .form-select, .form-check-input\"\n            );\n            fieldEl.classList.remove(\"o_has_error\");\n            for (const controlEl of controlEls) {\n                controlEl.classList.remove(\"is-invalid\");\n            }\n            if (invalidInputs.length || errorFields[fieldName]) {\n                fieldEl.classList.add(\"o_has_error\");\n                for (const controlEl of controlEls) {\n                    controlEl.classList.add(\"is-invalid\");\n                }\n                if (typeof errorFields[fieldName] === \"string\") {\n                    // update error message and show it.\n                    const popover = Popover.getOrCreateInstance(fieldEl, {\n                        content: errorFields[fieldName],\n                        trigger: \"hover\",\n                        container: \"body\",\n                        placement: \"top\",\n                    });\n                    popover.show();\n                }\n                formValid = false;\n            }\n        }\n        return formValid;\n    }\n\n    updateStatus(status, message) {\n        const resultEl = this.el.querySelector(\"#s_website_form_result, #o_website_form_result\"); // !compatibility\n\n        if (status === \"error\" && !message) {\n            message = _t(\"An error has occured, the form has not been sent.\");\n        }\n\n        const renderedEls = this.renderAt(\n            `website.s_website_form_status_${status}`,\n            {\n                message: message,\n            },\n            resultEl,\n            \"afterend\",\n            undefined,\n            false\n        );\n        // Handle cleanup manually to keep s_website_form_result in DOM.\n        this.registerCleanup(() => {\n            for (const el of renderedEls) {\n                const renderedResultEl = el.matches(\"#s_website_form_result\")\n                    ? el\n                    : el.querySelector(\"#s_website_form_result\");\n                renderedResultEl.replaceChildren();\n            }\n        });\n        resultEl.remove();\n    }\n    /**\n     * Renders the error message just below the respective input field\n     * to clearly indicate the erroneous field.\n     *\n     * @param {string} message The error message to be displayed.\n     * @param {HTMLElement} inputEl The input field where the error message\n     *     should be displayed.\n     */\n    updateStatusInline(message, inputEl) {\n        if (inputEl.parentElement.classList.contains(\"date\")) {\n            this.renderAt(\n                \"website.s_website_form_status_custom_error\",\n                {\n                    message,\n                },\n                inputEl.parentElement,\n                \"afterend\"\n            );\n        } else {\n            this.renderAt(\n                \"website.s_website_form_status_custom_error\",\n                {\n                    message,\n                },\n                inputEl.parentElement,\n                \"beforeend\"\n            );\n        }\n    }\n\n    /**\n     * Checks if the file input is valid: if the number of files uploaded\n     * and their size do not exceed the limits that were set.\n     *\n     * @param {HTMLElement} inputEl an input of type file\n     * @returns {Boolean} true if the input is valid, false otherwise.\n     */\n    isFileInputValid(inputEl) {\n        // Note: the `maxFilesNumber` and `maxFileSize` data-attributes may\n        // not always be present, if the Form comes from an older version\n        // for example.\n\n        // Checking the number of files.\n        const maxFilesNumber = inputEl.dataset.maxFilesNumber;\n        if (maxFilesNumber && inputEl.files.length > maxFilesNumber) {\n            // Store information to display the error message later.\n            const errorMessage = _t(\n                \"You have uploaded too many files(Maximum %s files).\",\n                maxFilesNumber\n            );\n            this.updateStatusInline(errorMessage, inputEl);\n            return false;\n        }\n        // Checking the files size.\n        const maxFileSize = inputEl.dataset.maxFileSize; // in megabytes.\n        const bytesInMegabyte = 1_000_000;\n        if (maxFileSize) {\n            for (const file of Object.values(inputEl.files)) {\n                if (file.size / bytesInMegabyte > maxFileSize) {\n                    const errorMessage = _t(\n                        \"Please fill in the form correctly. The file \u201c%(fileName)s\u201d is too large. (Maximum %(max)s MB)\",\n                        { fileName: file.name, max: maxFileSize }\n                    );\n                    this.updateStatusInline(errorMessage, inputEl);\n                    return false;\n                }\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Gets the user's field needed to be fetched to pre-fill the form.\n     *\n     * @returns {string[]} List of user's field that have to be fetched.\n     */\n    getUserPreFillFields() {\n        return [\"name\", \"phone\", \"email\", \"commercial_company_name\"];\n    }\n\n    /**\n     * Compares the value with the comparable (and the between) with\n     * comparator as a means to compare\n     *\n     * @param {string} comparator The way that $value and $comparable have\n     *      to be compared\n     * @param {string} [value] The value of the field\n     * @param {string} [comparable] The value to compare\n     * @param {string} [between] The maximum date value in case comparator\n     *      is between or !between\n     * @returns {boolean}\n     */\n    compareTo(comparator, value = \"\", comparable, between) {\n        // Value can be null when the compared field is supposed to be\n        // visible, but is not yet retrievable from the FormData() because\n        // the field was conditionally hidden. It can be considered empty.\n        if (value === null) {\n            value = \"\";\n        }\n\n        switch (comparator) {\n            case \"contains\":\n                return value.includes(comparable);\n            case \"!contains\":\n                return !value.includes(comparable);\n            case \"substring\":\n                return value.includes(comparable);\n            case \"!substring\":\n                return !value.includes(comparable);\n            case \"equal\":\n            case \"selected\":\n                return value === comparable;\n            case \"!equal\":\n            case \"!selected\":\n                return value !== comparable;\n            case \"set\":\n                return value;\n            case \"!set\":\n                return !value;\n            case \"greater\":\n                return parseFloat(value) > parseFloat(comparable);\n            case \"less\":\n                return parseFloat(value) < parseFloat(comparable);\n            case \"greater or equal\":\n                return parseFloat(value) >= parseFloat(comparable);\n            case \"less or equal\":\n                return parseFloat(value) <= parseFloat(comparable);\n            case \"fileSet\":\n                return value.name !== \"\";\n            case \"!fileSet\":\n                return value.name === \"\";\n        }\n\n        let format = \"\";\n        const xYearAgo = new Date();\n        if (value.includes(\":\")) {\n            format = localization.dateTimeFormat;\n        } else {\n            format = localization.dateFormat;\n            xYearAgo.setHours(0, 0, 0, 0);\n        }\n        // Date & Date Time comparison requires formatting the value\n        const dateTime = DateTime.fromFormat(value, format);\n        // If invalid, any value other than \"NaN\" would cause certain\n        // conditions to be broken.\n        value = dateTime.isValid ? dateTime.toUnixInteger() : NaN;\n\n        comparable = parseInt(comparable);\n        between = parseInt(between) || \"\";\n        switch (comparator) {\n            case \"dateEqual\":\n                return value === comparable;\n            case \"date!equal\":\n                return value !== comparable;\n            case \"before\":\n                return value < comparable;\n            case \"after\":\n                return value > comparable;\n            case \"equal or before\":\n                return value <= comparable;\n            case \"between\":\n                return value >= comparable && value <= between;\n            case \"!between\":\n                return !(value >= comparable && value <= between);\n            case \"equal or after\":\n                return value >= comparable;\n            case \"lessyears\":\n                xYearAgo.setFullYear(new Date().getFullYear() - comparable);\n                value = new Date(value * 1000);\n                return value > xYearAgo;\n        }\n    }\n\n    /**\n     * @param {HTMLElement} fieldEl the field we want to have a function\n     *      that calculates its visibility\n     * @returns {function} the function to be executed when we want to\n     *      recalculate the visibility of fieldEl\n     */\n    buildVisibilityFunction(fieldEl) {\n        const visibilityCondition = fieldEl.dataset.visibilityCondition;\n        const dependencyName = fieldEl.dataset.visibilityDependency;\n        const comparator = fieldEl.dataset.visibilityComparator;\n        const between = fieldEl.dataset.visibilityBetween;\n        return () => {\n            // To be visible, at least one field with the dependency name must be visible.\n            const dependencyVisibilityFunction =\n                this.visibilityFunctionByFieldName.get(dependencyName);\n            const dependencyIsVisible =\n                !dependencyVisibilityFunction || dependencyVisibilityFunction();\n            if (!dependencyIsVisible) {\n                return false;\n            }\n\n            const currentValueOfDependency = [\"contains\", \"!contains\"].includes(comparator)\n                ? this.lastFormData.getAll(dependencyName).join()\n                : this.lastFormData.get(dependencyName);\n            return this.compareTo(\n                comparator,\n                currentValueOfDependency,\n                visibilityCondition,\n                between\n            );\n        };\n    }\n\n    /**\n     * @param {HTMLElement} formEl the form from which we want to retrieve\n     *      the FormData, including the disabled fields.\n     * @returns {FormData} a FormData object containing also disabled fields\n     */\n    getFormDataIncludingDisabledFields(formEl) {\n        const disabledFields = formEl.querySelectorAll(\n            \"input:disabled, select:disabled, textarea:disabled\"\n        );\n        disabledFields.forEach((element) => {\n            element.removeAttribute(\"disabled\");\n        });\n        const formData = new FormData(formEl);\n        disabledFields.forEach((element) => {\n            element.setAttribute(\"disabled\", true);\n        });\n        return formData;\n    }\n\n    /**\n     * @private\n     * @param {HTMLElement} fieldEl The field whose validity needs\n     *      to be verified according to the requirements.\n     * @returns {boolean} A boolean indicating the validity of fieldEl\n     *      based on the set requirements.\n     */\n    requirementFunction(fieldEl) {\n        const {\n            requirementCondition: condition,\n            requirementComparator: comparator,\n            requirementBetween: between,\n        } = fieldEl.dataset;\n        const value = fieldEl.querySelector(\".s_website_form_input\").value;\n        if (!condition && comparator) {\n            return true;\n        }\n        if ([\"between\", \"!between\"].includes(comparator) && !between) {\n            return true;\n        }\n        if (!value.trim()) {\n            return true;\n        }\n        return this.compareTo(comparator, value, condition, between);\n    }\n\n    isFieldVisible(fieldEl) {\n        const isVisible = this.visibilityFunctionByFieldEl.get(fieldEl);\n        return isVisible ? !!isVisible() : true;\n    }\n\n    isInputVisible(inputEl) {\n        return this.isFieldVisible(inputEl.closest(\".s_website_form_field\"));\n    }\n\n    /**\n     * Creates a block containing the file name and a cross to delete it.\n     *\n     * @param {Object} fileDetails the details of the file being uploaded\n     * @param {HTMLElement} filesZoneEl the zone where the file blocks are\n     *      displayed\n     */\n    createFileBlock(fileDetails, filesZoneEl) {\n        this.renderAt(\n            \"website.file_block\",\n            { fileName: fileDetails.name },\n            filesZoneEl,\n            \"beforeend\",\n            (els) => (els[0].fileDetails = fileDetails)\n        );\n    }\n\n    /**\n     * Creates the file upload button (= a button to replace the file input,\n     * in order to modify its text content more easily).\n     *\n     * @param {HTMLElement} inputEl the file input\n     */\n    createAddFilesButton(inputEl) {\n        const addFilesButtonEl = document.createElement(\"INPUT\");\n        addFilesButtonEl.classList.add(\"o_add_files_button\", \"form-control\");\n        addFilesButtonEl.type = \"button\";\n        addFilesButtonEl.value = inputEl.hasAttribute(\"multiple\")\n            ? _t(\"Add Files\")\n            : _t(\"Replace File\");\n        inputEl.parentNode.insertBefore(addFilesButtonEl, inputEl);\n        inputEl.classList.add(\"d-none\");\n    }\n\n    /**\n     * Calculates the visibility of the fields at each input event on the\n     * form (this method should be debounced in the start).\n     */\n    onFieldInput() {\n        // Implicitly updates DOM.\n        // Generates a new snapshot of the current form data, including disabled\n        // fields, which is necessary for visibility calculations.\n        this.lastFormData = this.getFormDataIncludingDisabledFields(this.el);\n    }\n\n    /**\n     * Called when files are uploaded: updates the button text content,\n     * displays the file blocks (containing the files name and a cross to\n     * delete them) and manages the files.\n     *\n     * @param {Event} ev\n     */\n    changeFile(ev) {\n        const fileInputEl = ev.currentTarget;\n        const fieldEl = fileInputEl.closest(\".s_website_form_field\");\n        const uploadedFiles = fileInputEl.files;\n        const addFilesButtonEl = fieldEl.querySelector(\".o_add_files_button\");\n\n        // The zone where the file blocks are displayed.\n        const filesZoneEl = fieldEl.querySelector(\".o_files_zone\");\n        // Update the button text content.\n        if (!addFilesButtonEl) {\n            this.createAddFilesButton(fileInputEl);\n        }\n\n        // Create a list to keep track of the files.\n        if (!fileInputEl.fileList) {\n            fileInputEl.fileList = new DataTransfer();\n        }\n\n        // If only one file can be uploaded, delete the previous file.\n        if (!fileInputEl.hasAttribute(\"multiple\") && uploadedFiles.length > 0) {\n            fileInputEl.fileList = new DataTransfer();\n            const fileBlockEl = fieldEl.querySelector(\".o_file_block\");\n            if (fileBlockEl) {\n                fileBlockEl.remove();\n            }\n        }\n\n        // Add the uploaded files if they are not already there.\n        for (const newFile of uploadedFiles) {\n            if (\n                ![...fileInputEl.fileList.files].some(\n                    (file) =>\n                        newFile.name === file.name &&\n                        newFile.size === file.size &&\n                        newFile.type === file.type\n                )\n            ) {\n                fileInputEl.fileList.items.add(newFile);\n                const fileDetails = { name: newFile.name, size: newFile.size, type: newFile.type };\n                this.createFileBlock(fileDetails, filesZoneEl);\n            }\n        }\n        // Update the input files.\n        fileInputEl.files = fileInputEl.fileList.files;\n    }\n\n    /**\n     * Called when a file is deleted by clicking on the cross on the block\n     * describing it.\n     *\n     * @param {Event} ev\n     */\n    clickFileDelete(ev) {\n        if (!ev.target.closest(\".o_file_delete\")) {\n            return;\n        }\n        const fileBlockEl = ev.target.closest(\".o_file_block\");\n        const fieldEl = fileBlockEl.closest(\".s_website_form_field\");\n        const fileInputEl = fieldEl.querySelector(\"input[type=file]\");\n        const fileDetails = fileBlockEl.fileDetails;\n        const addFilesButtonEl = fieldEl.querySelector(\".o_add_files_button\");\n\n        // Create a new file list containing the remaining files.\n        const newFileList = new DataTransfer();\n        for (const file of Object.values(fileInputEl.fileList.files)) {\n            if (\n                file.name !== fileDetails.name ||\n                file.size !== fileDetails.size ||\n                file.type !== fileDetails.type\n            ) {\n                newFileList.items.add(file);\n            }\n        }\n        // Update the input lists and remove the file block.\n        Object.assign(fileInputEl, { fileList: newFileList, files: newFileList.files });\n        fileBlockEl.remove();\n\n        // Restore the file input if there are no files uploaded and update\n        // the fields visibility.\n        if (!newFileList.files.length) {\n            fileInputEl.classList.remove(\"d-none\");\n            addFilesButtonEl.remove();\n        }\n    }\n\n    /**\n     * Detects when the fake input file button is clicked to simulate a\n     * click on the real input.\n     *\n     * @param {MouseEvent} ev\n     */\n    clickAddFilesButton(ev) {\n        const fileInputEl = ev.target.parentNode.querySelector(\"input[type=file]\");\n        fileInputEl.click();\n    }\n    /**\n     * Removes the error message displayed below the input field\n     * when the form is submitted with errors or for resetting the form.\n     */\n    removeErrorMessages() {\n        this.el.querySelectorAll(\".s_website_form_custom_error\").forEach((error) => {\n            error.remove();\n        });\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.form\", Form);\n", "/**\n    This code has been more that widely inspired by easyZoom library.\n\n    Copyright 2013 Matt Hinchliffe\n\n    Permission is hereby granted, free of charge, to any person obtaining\n    a copy of this software and associated documentation files (the\n    \"Software\"), to deal in the Software without restriction, including\n    without limitation the rights to use, copy, modify, merge, publish,\n    distribute, sublicense, and/or sell copies of the Software, and to\n    permit persons to whom the Software is furnished to do so, subject to\n    the following conditions:\n\n    The above copyright notice and this permission notice shall be\n    included in all copies or substantial portions of the Software.\n**/\n\nvar dw, dh, rw, rh, lx, ly;\n\nvar defaults = {\n\n    // Attribute to retrieve the zoom image URL from.\n    linkTag: 'a',\n    linkAttribute: 'data-zoom-image',\n\n    // event to trigger zoom\n    event: 'click', //or mouseenter\n\n    // Timer before trigger zoom\n    timer: 0,\n\n    // Prevent clicks on the zoom image link.\n    preventClicks: true,\n\n    // disable on mobile\n    disabledOnMobile: true,\n\n    // Callback function to execute before the flyout is displayed.\n    beforeShow: $.noop,\n\n    // Callback function to execute before the flyout is removed.\n    beforeHide: $.noop,\n\n    // Callback function to execute when the flyout is displayed.\n    onShow: $.noop,\n\n    // Callback function to execute when the flyout is removed.\n    onHide: $.noop,\n\n    // Callback function to execute when the cursor is moved while over the image.\n    onMove: $.noop,\n\n    // Callback function to execute when the flyout is attached to the target.\n    beforeAttach: $.noop\n\n};\n\n/**\n * ZoomOdoo\n * @constructor\n * @param {Object} target\n * @param {Object} options (Optional)\n */\nfunction ZoomOdoo(target, options) {\n    this.$target = $(target);\n    this.opts = $.extend({}, defaults, options, this.$target.data());\n\n    if (this.isOpen === undefined) {\n        this._init();\n    }\n}\n\n/**\n * Init\n * @private\n */\nZoomOdoo.prototype._init = function () {\n    if (window.outerWidth > 467 || !this.opts.disabledOnMobile) {\n        this.$link  = this.$target.find(this.opts.linkTag).length && this.$target.find(this.opts.linkTag) || this.$target;\n        this.$image  = this.$target.find('img').length && this.$target.find('img') || this.$target;\n        this.$flyout = $('<div class=\"zoomodoo-flyout\" />');\n\n        var $attach = this.$target;\n        if (this.opts.attach !== undefined && this.$target.closest(this.opts.attach).length) {\n            $attach = this.$target.closest(this.opts.attach);\n        }\n        $attach.parent().on('mousemove.zoomodoo touchmove.zoomodoo', $.proxy(this._onMove, this));\n        $attach.parent().on('mouseleave.zoomodoo touchend.zoomodoo', $.proxy(this._onLeave, this));\n        this.$target.on(this.opts.event + '.zoomodoo touchstart.zoomodoo', $.proxy(this._onEnter, this));\n\n        if (this.opts.preventClicks) {\n            this.$target.on('click.zoomodoo', function (e) { e.preventDefault(); });\n        } else {\n            var self = this;\n            this.$target.on('click.zoomodoo', function () { self.hide(); self.$target.unbind(); });\n        }\n    }\n};\n\n/**\n * Show\n * @param {MouseEvent|TouchEvent} e\n * @param {Boolean} testMouseOver (Optional)\n */\nZoomOdoo.prototype.show = function (e, testMouseOver) {\n    var w1, h1, w2, h2;\n    var self = this;\n\n    if (this.opts.beforeShow.call(this) === false) return;\n\n    if (!this.isReady) {\n        return this._loadImage(this.$link.attr(this.opts.linkAttribute), function () {\n            if (self.isMouseOver || !testMouseOver) {\n                self.show(e);\n            }\n        });\n    }\n\n    var $attach = this.$target;\n    if (this.opts.attach !== undefined && this.$target.closest(this.opts.attach).length) {\n        $attach = this.$target.closest(this.opts.attach);\n    }\n\n    // Prevents having multiple zoom flyouts\n    $attach.parent().find('.zoomodoo-flyout').remove();\n    this.$flyout.removeAttr('style');\n    $attach.parent().append(this.$flyout);\n\n    if (this.opts.attachToTarget) {\n        this.opts.beforeAttach.call(this);\n\n        // Be sure that the flyout is at top 0, left 0 to ensure correct computation\n        // e.g. employees kanban on dashboard\n        this.$flyout.css('position', 'fixed');\n        var flyoutOffset = this.$flyout.offset();\n        if (flyoutOffset.left > 0) {\n            var flyoutLeft = parseFloat(this.$flyout.css('left').replace('px',''));\n            this.$flyout.css('left', flyoutLeft - flyoutOffset.left + 'px');\n        }\n        if (flyoutOffset.top > 0) {\n            var flyoutTop = parseFloat(this.$flyout.css('top').replace('px',''));\n            this.$flyout.css('top', flyoutTop - flyoutOffset.top + 'px');\n        }\n\n        if(this.$zoom.height() < this.$flyout.height()) {\n             this.$flyout.css('height', this.$zoom.height() + 'px');\n        }\n        if(this.$zoom.width() < this.$flyout.width()) {\n             this.$flyout.css('width', this.$zoom.width() + 'px');\n        }\n\n        var offset = this.$target.offset();\n        var left = offset.left - this.$flyout.width();\n        var top = offset.top;\n\n        // Position the zoom on the right side of the target\n        // if there's not enough room on the left\n        if(left < 0) {\n            if(offset.left < ($(document).width() / 2)) {\n                left = offset.left + this.$target.width();\n            } else {\n                left = 0;\n            }\n        }\n\n        // Prevents the flyout to overflow\n        if(left + this.$flyout.width() > $(document).width()) {\n            this.$flyout.css('width',  $(document).width() - left + 'px');\n        } else if(left === 0) { // Limit the max width if displayed on the left\n            this.$flyout.css('width', offset.left + 'px');\n        }\n\n        // Prevents the zoom to be displayed outside the current viewport\n        if((top + this.$flyout.height()) > $(document).height()) {\n            top = $(document).height() - this.$flyout.height();\n        }\n\n        this.$flyout.css('transform', 'translate3d(' + left + 'px, ' + top + 'px, 0px)');\n    } else {\n        // Computing flyout max-width depending to the available space on the right to avoid overflow-x issues\n        // e.g. width too high so a right zoomed element is not visible (need to scroll on x axis)\n        var rightAvailableSpace = document.body.clientWidth - this.$flyout[0].getBoundingClientRect().left;\n        this.$flyout.css('max-width', rightAvailableSpace);\n    }\n\n    w1 = this.$target[0].offsetWidth;\n    h1 = this.$target[0].offsetHeight;\n\n    w2 = this.$flyout.width();\n    h2 = this.$flyout.height();\n\n    dw = this.$zoom.width() - w2;\n    dh = this.$zoom.height() - h2;\n\n    // For the case where the zoom image is actually smaller than\n    // the flyout.\n    if (dw < 0) dw = 0;\n    if (dh < 0) dh = 0;\n\n    rw = dw / w1;\n    rh = dh / h1;\n\n    this.isOpen = true;\n\n    this.opts.onShow.call(this);\n\n    if (e) {\n        this._move(e);\n    }\n};\n\n/**\n * On enter\n * @private\n * @param {Event} e\n */\nZoomOdoo.prototype._onEnter = function (e) {\n    var self = this;\n    var touches = e.originalEvent.touches;\n    e.preventDefault();\n    this.isMouseOver = true;\n\n    setTimeout(function () {\n        if (self.isMouseOver && (!touches || touches.length === 1)) {\n            self.show(e, true);\n        }\n      }, this.opts.timer);\n\n};\n\n/**\n * On move\n * @private\n * @param {Event} e\n */\nZoomOdoo.prototype._onMove = function (e) {\n    if (!this.isOpen) return;\n\n    e.preventDefault();\n    this._move(e);\n};\n\n/**\n * On leave\n * @private\n */\nZoomOdoo.prototype._onLeave = function () {\n    this.isMouseOver = false;\n    if (this.isOpen) {\n        this.hide();\n    }\n};\n\n/**\n * On load\n * @private\n * @param {Event} e\n */\nZoomOdoo.prototype._onLoad = function (e) {\n    // IE may fire a load event even on error so test the image dimensions\n    if (!e.currentTarget.width) return;\n\n    this.isReady = true;\n\n    this.$flyout.html(this.$zoom);\n\n    if (e.data.call) {\n        e.data();\n    }\n};\n\n/**\n * Load image\n * @private\n * @param {String} href\n * @param {Function} callback\n */\nZoomOdoo.prototype._loadImage = function (href, callback) {\n    var zoom = new Image();\n\n    this.$zoom = $(zoom).on('load', callback, $.proxy(this._onLoad, this));\n\n    zoom.style.position = 'absolute';\n    zoom.src = href;\n};\n\n/**\n * Move\n * @private\n * @param {Event} e\n */\nZoomOdoo.prototype._move = function (e) {\n    if (e.type.indexOf('touch') === 0) {\n        var touchlist = e.touches || e.originalEvent.touches;\n        lx = touchlist[0].pageX;\n        ly = touchlist[0].pageY;\n    } else {\n        lx = e.pageX || lx;\n        ly = e.pageY || ly;\n    }\n\n    var offset  = this.$target.offset();\n    var pt = ly - offset.top;\n    var pl = lx - offset.left;\n    var xt = Math.ceil(pt * rh);\n    var xl = Math.ceil(pl * rw);\n\n    // Close if outside\n    if (!this.opts.attachToTarget && (xl < 0 || xt < 0 || xl > dw || xt > dh || lx > (offset.left + this.$target.outerWidth()))) {\n        this.hide();\n    } else {\n        var top = xt * -1;\n        var left = xl * -1;\n\n        this.$zoom.css({\n            top: top,\n            left: left\n        });\n\n        this.opts.onMove.call(this, top, left);\n    }\n\n};\n\n/**\n * Hide\n */\nZoomOdoo.prototype.hide = function () {\n    if (!this.isOpen) return;\n    if (this.opts.beforeHide.call(this) === false) return;\n\n    this.$flyout.detach();\n    this.isOpen = false;\n\n    this.opts.onHide.call(this);\n};\n\n// jQuery plugin wrapper\n$.fn.zoomOdoo = function (options) {\n    return this.each(function () {\n        var api = $.data(this, 'zoomOdoo');\n\n        if (!api) {\n            $.data(this, 'zoomOdoo', new ZoomOdoo(this, options));\n        } else if (api.isOpen === undefined) {\n            api._init();\n        }\n    });\n};\n", "/**\n * Grep `_detectNavbar`: the dynamic navbar's dropdown positioning was activated\n * to prevent sub-menus overflow. This positioning will use the default BS\n * offsets to position sub-menus leading to a small gap that hides them when\n * hovered (on \"Hover\" mode). The goal here is to prevent this offset when the\n * target is inside a navbar.\n */\nconst bsGetOffsetFunction = Dropdown.prototype._getOffset;\nDropdown.prototype._getOffset = function () {\n    const offset = bsGetOffsetFunction.apply(this, arguments);\n    if (this._element.closest(\".o_hoverable_dropdown .navbar\")) {\n        return [offset[0], 0];\n    }\n    return offset;\n};\n", "import { intersection } from \"@web/core/utils/arrays\";\nimport { _t, appTranslateFn } from \"@web/core/l10n/translation\";\nimport { renderToElement } from \"@web/core/utils/render\";\nimport { App, Component } from \"@odoo/owl\";\nimport { getTemplate } from \"@web/core/templates\";\nimport { UrlAutoComplete } from \"@website/components/autocomplete_with_pages/url_autocomplete\";\nimport * as urlUtils from \"@html_editor/utils/url\";\nimport { patch } from \"@web/core/utils/patch\";\n\n/**\n * Allows to load anchors from a page.\n *\n * @param {string} url\n * @param {Node} body the editable for which to recover anchors\n * @returns {Deferred<string[]>}\n */\nfunction loadAnchors(url, body) {\n    return new Promise(function (resolve, reject) {\n        if (url === window.location.pathname || url[0] === \"#\") {\n            resolve(body ? body.outerHTML : document.body.outerHTML);\n        } else if (url.length && !url.startsWith(\"http\")) {\n            // TODO: Might be broken with ReplaceMedia (NBY) and LinkTools\n            fetch(window.location.origin + url)\n                .then((response) => response.text())\n                .then((text) => {\n                    const parser = new DOMParser();\n                    const doc = parser.parseFromString(text, \"text/html\");\n                    return doc.body;\n                })\n                .then(resolve, reject);\n        } else {\n            // avoid useless query\n            resolve();\n        }\n    })\n        .then(function (response) {\n            const fragment = new DOMParser().parseFromString(response, \"text/html\");\n            const anchorEls = fragment.querySelectorAll(\n                `[id][data-anchor=\"true\"], .modal[id][data-display=\"onClick\"]`\n            );\n            const anchors = Array.from(anchorEls).map((el) => \"#\" + el.id);\n\n            // Always suggest the top and the bottom of the page as internal link\n            // anchor even if the header and the footer are not in the DOM. Indeed,\n            // the \"scrollTo\" function handles the scroll towards those elements\n            // even when they are not in the DOM.\n            if (!anchors.includes(\"#top\")) {\n                anchors.unshift(\"#top\");\n            }\n            if (!anchors.includes(\"#bottom\")) {\n                anchors.push(\"#bottom\");\n            }\n            return anchors;\n        })\n        .catch((error) => {\n            console.debug(error);\n            return [];\n        });\n}\n\n/**\n * Allows the given input to propose existing website URLs.\n *\n * @param {HTMLInputElement} input\n */\nfunction autocompleteWithPages(input, options = {}, env = undefined) {\n    const owlApp = new App(UrlAutoComplete, {\n        env: env || Component.env,\n        dev: env ? env.debug : Component.env.debug,\n        getTemplate,\n        props: {\n            options,\n            loadAnchors,\n            targetDropdown: input,\n        },\n        translatableAttributes: [\"data-tooltip\"],\n        translateFn: appTranslateFn,\n    });\n\n    const container = document.createElement(\"div\");\n    container.classList.add(\"ui-widget\", \"ui-autocomplete\", \"ui-widget-content\", \"border-0\");\n    document.body.appendChild(container);\n    owlApp.mount(container);\n    return () => {\n        owlApp.destroy();\n        container.remove();\n    };\n}\n\n/**\n * @param {jQuery} $element\n * @param {jQuery} [$excluded]\n */\nfunction onceAllImagesLoaded($element, $excluded) {\n    var defs = Array.from($element.find(\"img\").addBack(\"img\")).map((img) => {\n        if (img.complete || ($excluded && ($excluded.is(img) || $excluded.has(img).length))) {\n            return; // Already loaded\n        }\n        var def = new Promise(function (resolve, reject) {\n            $(img).one(\"load\", function () {\n                resolve();\n            });\n        });\n        return def;\n    });\n    return Promise.all(defs);\n}\n\n/**\n * @deprecated\n * @todo create Dialog.prompt instead of this\n */\nfunction prompt(options, _qweb) {\n    /**\n     * A bootstrapped version of prompt() albeit asynchronous\n     * This was built to quickly prompt the user with a single field.\n     * For anything more complex, please use editor.Dialog class\n     *\n     * Usage Ex:\n     *\n     * website.prompt(\"What... is your quest?\").then(function (answer) {\n     *     arthur.reply(answer || \"To seek the Holy Grail.\");\n     * });\n     *\n     * website.prompt({\n     *     select: \"Please choose your destiny\",\n     *     init: function () {\n     *         return [ [0, \"Sub-Zero\"], [1, \"Robo-Ky\"] ];\n     *     }\n     * }).then(function (answer) {\n     *     mame_station.loadCharacter(answer);\n     * });\n     *\n     * @param {Object|String} options A set of options used to configure the prompt or the text field name if string\n     * @param {String} [options.window_title=''] title of the prompt modal\n     * @param {String} [options.input] tell the modal to use an input text field, the given value will be the field title\n     * @param {String} [options.textarea] tell the modal to use a textarea field, the given value will be the field title\n     * @param {String} [options.select] tell the modal to use a select box, the given value will be the field title\n     * @param {Object} [options.default=''] default value of the field\n     * @param {Function} [options.init] optional function that takes the `field` (enhanced with a fillWith() method) and the `dialog` as parameters [can return a promise]\n     */\n    if (typeof options === \"string\") {\n        options = {\n            text: options,\n        };\n    }\n    if (typeof _qweb === \"undefined\") {\n        _qweb = \"website.prompt\";\n    }\n    options = Object.assign(\n        {\n            window_title: \"\",\n            field_name: \"\",\n            default: \"\", // dict notation for IE<9\n            init: function () {},\n            btn_primary_title: _t(\"Create\"),\n            btn_secondary_title: _t(\"Cancel\"),\n        },\n        options || {}\n    );\n\n    var type = intersection(Object.keys(options), [\"input\", \"textarea\", \"select\"]);\n    type = type.length ? type[0] : \"input\";\n    options.field_type = type;\n    options.field_name = options.field_name || options[type];\n\n    var def = new Promise(function (resolve, reject) {\n        var dialog = $(renderToElement(_qweb, options)).appendTo(\"body\");\n        options.$dialog = dialog;\n        var field = dialog.find(options.field_type).first();\n        field.val(options[\"default\"]); // dict notation for IE<9\n        field.fillWith = function (data) {\n            if (field.is(\"select\")) {\n                var select = field[0];\n                data.forEach(function (item) {\n                    select.options[select.options.length] = new window.Option(item[1], item[0]);\n                });\n            } else {\n                field.val(data);\n            }\n        };\n        var init = options.init(field, dialog);\n        Promise.resolve(init).then(function (fill) {\n            if (fill) {\n                field.fillWith(fill);\n            }\n            dialog.modal(\"show\");\n            field.focus();\n            dialog.on(\"click\", \".btn-primary\", function () {\n                var backdrop = $(\".modal-backdrop\");\n                resolve({ val: field.val(), field: field, dialog: dialog });\n                dialog.modal(\"hide\").remove();\n                backdrop.remove();\n            });\n        });\n        dialog.on(\"hidden.bs.modal\", function () {\n            var backdrop = $(\".modal-backdrop\");\n            reject();\n            dialog.remove();\n            backdrop.remove();\n        });\n        if (field.is('input[type=\"text\"], select')) {\n            field.keypress(function (e) {\n                if (e.key === \"Enter\") {\n                    e.preventDefault();\n                    dialog.find(\".btn-primary\").trigger(\"click\");\n                }\n            });\n        }\n    });\n\n    return def;\n}\n\nfunction websiteDomain(self) {\n    var websiteID;\n    self.trigger_up(\"context_get\", {\n        callback: function (ctx) {\n            websiteID = ctx[\"website_id\"];\n        },\n    });\n    return [\"|\", [\"website_id\", \"=\", false], [\"website_id\", \"=\", websiteID]];\n}\n\n/**\n * Checks if the 2 given URLs are the same, to prevent redirecting uselessly\n * from one to another.\n * It will consider naked URL and `www` URL as the same URL.\n * It will consider `https` URL `http` URL as the same URL.\n *\n * @param {string} url1\n * @param {string} url2\n * @returns {Boolean}\n */\nfunction isHTTPSorNakedDomainRedirection(url1, url2) {\n    try {\n        url1 = new URL(url1).host;\n        url2 = new URL(url2).host;\n    } catch {\n        // Incorrect URL, `false` URL..\n        return false;\n    }\n    return url1 === url2 || url1.replace(/^www\\./, \"\") === url2.replace(/^www\\./, \"\");\n}\n\nexport function sendRequest(route, params) {\n    function _addInput(form, name, value) {\n        const param = document.createElement(\"input\");\n        param.setAttribute(\"type\", \"hidden\");\n        param.setAttribute(\"name\", name);\n        param.setAttribute(\"value\", value);\n        form.appendChild(param);\n    }\n\n    const form = document.createElement(\"form\");\n    form.setAttribute(\"action\", route);\n    form.setAttribute(\"method\", params.method || \"POST\");\n    // This is an exception for the 404 page create page button, in backend we\n    // want to open the response in the top window not in the iframe.\n    if (params.forceTopWindow) {\n        form.setAttribute(\"target\", \"_top\");\n    }\n\n    if (odoo.csrf_token) {\n        _addInput(form, \"csrf_token\", odoo.csrf_token);\n    }\n\n    for (const key in params) {\n        const value = params[key];\n        if (Array.isArray(value) && value.length) {\n            for (const val of value) {\n                _addInput(form, key, val);\n            }\n        } else {\n            _addInput(form, key, value);\n        }\n    }\n\n    document.body.appendChild(form);\n    form.submit();\n}\n\n/**\n * Converts a base64 SVG into a base64 PNG.\n *\n * @param {string|HTMLImageElement} src - an URL to a SVG or a *loaded* image\n *      with such an URL. This allows the call to potentially be a bit more\n *      efficient in that second case.\n * @returns {Promise<string>} a base64 PNG (as result of a Promise)\n */\nexport async function svgToPNG(src) {\n    return _exportToPNG(src, \"svg+xml\");\n}\n\n/**\n * Converts a base64 WEBP into a base64 PNG.\n *\n * @param {string|HTMLImageElement} src - an URL to a WEBP or a *loaded* image\n *     with such an URL. This allows the call to potentially be a bit more\n *     efficient in that second case.\n * @returns {Promise<string>} a base64 PNG (as result of a Promise)\n */\nexport async function webpToPNG(src) {\n    return _exportToPNG(src, \"webp\");\n}\n\n/**\n * Converts a formatted base64 image into a base64 PNG.\n *\n * @private\n * @param {string|HTMLImageElement} src - an URL to a image or a *loaded* image\n *     with such an URL. This allows the call to potentially be a bit more\n *     efficient in that second case.\n * @param {string} format - the format of the image\n * @returns {Promise<string>} a base64 PNG (as result of a Promise)\n */\nasync function _exportToPNG(src, format) {\n    function checkImg(imgEl) {\n        // Firefox does not support drawing SVG to canvas unless it has width\n        // and height attributes set on the root <svg>.\n        return imgEl.naturalHeight !== 0;\n    }\n    function toPNGViaCanvas(imgEl) {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = imgEl.width;\n        canvas.height = imgEl.height;\n        canvas.getContext(\"2d\").drawImage(imgEl, 0, 0);\n        return canvas.toDataURL(\"image/png\");\n    }\n\n    // In case we receive a loaded image and that this image is not problematic,\n    // we can convert it to PNG directly.\n    if (src instanceof HTMLImageElement) {\n        const loadedImgEl = src;\n        if (checkImg(loadedImgEl)) {\n            return toPNGViaCanvas(loadedImgEl);\n        }\n        src = loadedImgEl.src;\n    }\n\n    // At this point, we either did not receive a loaded image or the received\n    // loaded image is problematic => we have to do some asynchronous code.\n    return new Promise((resolve) => {\n        const imgEl = new Image();\n        imgEl.onload = () => {\n            if (format !== \"svg+xml\" || checkImg(imgEl)) {\n                resolve(imgEl);\n                return;\n            }\n\n            // Set arbitrary height on image and attach it to the DOM to force\n            // width computation.\n            imgEl.height = 1000;\n            imgEl.style.opacity = 0;\n            document.body.appendChild(imgEl);\n\n            const request = new XMLHttpRequest();\n            request.open(\"GET\", imgEl.src, true);\n            request.onload = () => {\n                // Convert the data URI to a SVG element\n                const parser = new DOMParser();\n                const result = parser.parseFromString(request.responseText, \"text/xml\");\n                const svgEl = result.getElementsByTagName(\"svg\")[0];\n\n                // Add the attributes Firefox needs and remove the image from\n                // the DOM.\n                svgEl.setAttribute(\"width\", imgEl.width);\n                svgEl.setAttribute(\"height\", imgEl.height);\n                imgEl.remove();\n\n                // Convert the SVG element to a data URI\n                const svg64 = btoa(new XMLSerializer().serializeToString(svgEl));\n                const finalImg = new Image();\n                finalImg.onload = () => {\n                    resolve(finalImg);\n                };\n                finalImg.src = `data:image/svg+xml;base64,${svg64}`;\n            };\n            request.send();\n        };\n        imgEl.src = src;\n    }).then((loadedImgEl) => toPNGViaCanvas(loadedImgEl));\n}\n\n/**\n * Bootstraps an \"empty\" Google Maps iframe.\n *\n * @returns {HTMLIframeElement}\n */\nexport function generateGMapIframe() {\n    const iframeEl = document.createElement(\"iframe\");\n    iframeEl.classList.add(\"s_map_embedded\", \"o_not_editable\");\n    iframeEl.setAttribute(\"width\", \"100%\");\n    iframeEl.setAttribute(\"height\", \"100%\");\n    iframeEl.setAttribute(\"frameborder\", \"0\");\n    iframeEl.setAttribute(\"scrolling\", \"no\");\n    iframeEl.setAttribute(\"marginheight\", \"0\");\n    iframeEl.setAttribute(\"marginwidth\", \"0\");\n    iframeEl.setAttribute(\"src\", \"about:blank\");\n    iframeEl.setAttribute(\"aria-label\", _t(\"Map\"));\n    return iframeEl;\n}\n\n/**\n * Generates a Google Maps URL based on the given parameter.\n *\n * @param {DOMStringMap} dataset\n * @returns {string} a Google Maps URL\n */\nexport function generateGMapLink(dataset) {\n    return (\n        \"https://maps.google.com/maps?q=\" +\n        encodeURIComponent(dataset.mapAddress) +\n        \"&t=\" +\n        encodeURIComponent(dataset.mapType) +\n        \"&z=\" +\n        encodeURIComponent(dataset.mapZoom) +\n        \"&ie=UTF8&iwloc=&output=embed\"\n    );\n}\n\n/**\n * Checks if the edited content is currently previewed as in a mobile device.\n *\n * @param {Object} self - context object (\"this\")\n * @returns {boolean}\n */\nfunction isMobile(self) {\n    let isMobile;\n    self.trigger_up(\"service_context_get\", {\n        callback: (ctx) => {\n            isMobile = ctx[\"isMobile\"];\n        },\n    });\n\n    return isMobile;\n}\n\n/**\n * Returns the parsed data coming from the data-for element for the given form.\n *\n * @param {string} formId\n * @param {HTMLElement} parentEl\n * @returns {Object|undefined} the parsed data\n */\nfunction getParsedDataFor(formId, parentEl) {\n    const dataForEl = parentEl.querySelector(`[data-for='${formId}']`);\n    if (!dataForEl) {\n        return;\n    }\n    return JSON.parse(\n        dataForEl.dataset.values\n            // replaces `True` by `true` if they are after `,` or `:` or `[`\n            .replace(/([,:[]\\s*)True/g, \"$1true\")\n            // replaces `False` and `None` by `\"\"` if they are after `,` or `:` or `[`\n            .replace(/([,:[]\\s*)(False|None)/g, '$1\"\"')\n            // replaces the `'` by `\"` if they are before `,` or `:` or `]` or `}`\n            .replace(/'(\\s*[,:\\]}])/g, '\"$1')\n            // replaces the `'` by `\"` if they are after `{` or `[` or `,` or `:`\n            .replace(/([{[:,]\\s*)'/g, '$1\"')\n    );\n}\n\n/**\n * Deep clones children or parses a string into elements, with or without\n * <script> elements.\n *\n * @param {DocumentFragment|HTMLElement|String} content\n * @param {Boolean} [keepScripts=false] - whether to keep script tags or not.\n * @returns {DocumentFragment}\n */\nexport function cloneContentEls(content, keepScripts = false) {\n    let copyFragment;\n    if (typeof content === \"string\") {\n        copyFragment = new Range().createContextualFragment(content);\n    } else {\n        copyFragment = new DocumentFragment();\n        const els = [...content.children].map((el) => el.cloneNode(true));\n        copyFragment.append(...els);\n    }\n    if (!keepScripts) {\n        copyFragment.querySelectorAll(\"script\").forEach((scriptEl) => scriptEl.remove());\n    }\n    return copyFragment;\n}\n\n/**\n * Checks SEO data and notifies if either the page title or description is not\n * set.\n *\n * @param {Object} seo_data - The SEO data to check.\n * @param {Component} OptimizeSEODialog - Dialog to be displayed\n * @param {Object} services - Services object which will be used to display\n * notifications and dialog.\n */\nexport function checkAndNotifySEO(seo_data, OptimizeSEODialog, services) {\n    if (seo_data) {\n        let message;\n        if (!seo_data.website_meta_title) {\n            message = _t(\"Page title not set.\");\n        } else if (!seo_data.website_meta_description) {\n            message = _t(\"Page description not set.\");\n        }\n        if (message) {\n            const closeNotification = services.notification.add(message, {\n                type: \"warning\",\n                sticky: false,\n                buttons: [\n                    {\n                        name: _t(\"Optimize SEO\"),\n                        onClick: () => {\n                            services.dialog.add(OptimizeSEODialog);\n                            closeNotification();\n                        },\n                    },\n                ],\n            });\n        }\n    }\n}\n\n/**\n * Converts a string into a URL-friendly slug.\n *\n * @param {string} value - The string to slugify.\n * @returns {string} The slugified string.\n */\nexport function slugify(value) {\n    // `NFKD` as in `http_routing` python `slugify()`\n    return !value\n        ? \"\"\n        : value\n              .trim()\n              .normalize(\"NFKD\")\n              .toLowerCase()\n              .replace(/['\u2019]/g, \"-\") // Replace apostrophes with hyphens\n              .replace(/\\s+/g, \"-\") // Replace spaces with -\n              .replace(/[^\\w-]+/g, \"\") // Remove all non-word chars\n              .replace(/--+/g, \"-\"); // Replace multiple - with single -\n}\n\npatch(urlUtils, {\n    isAbsoluteURLInCurrentDomain(url, env = null) {\n        const res = super.isAbsoluteURLInCurrentDomain(url, env);\n        if (res) {\n            return true;\n        }\n\n        const w = env?.services.website.currentWebsite;\n        if (!w) {\n            return false;\n        }\n\n        // Make sure that while being on abc.odoo.com, if you edit a link and\n        // enter an absolute URL using your real domain, it is still considered\n        // to be added as relative, preferably.\n        // In the past, you could not edit your website from abc.odoo.com if you\n        // properly configured your real domain already.\n        let origin;\n        try {\n            // Needed: \"http:\" would crash\n            origin = new URL(url, window.location.origin).origin;\n        } catch {\n            return false;\n        }\n        return `${origin}/`.startsWith(w.domain);\n    },\n});\n\nexport default {\n    loadAnchors: loadAnchors,\n    autocompleteWithPages: autocompleteWithPages,\n    onceAllImagesLoaded: onceAllImagesLoaded,\n    prompt: prompt,\n    sendRequest: sendRequest,\n    websiteDomain: websiteDomain,\n    isHTTPSorNakedDomainRedirection: isHTTPSorNakedDomainRedirection,\n    svgToPNG: svgToPNG,\n    webpToPNG: webpToPNG,\n    generateGMapIframe: generateGMapIframe,\n    generateGMapLink: generateGMapLink,\n    isMobile: isMobile,\n    getParsedDataFor: getParsedDataFor,\n    cloneContentEls: cloneContentEls,\n    checkAndNotifySEO: checkAndNotifySEO,\n    slugify: slugify,\n};\n", "import { AutoComplete } from \"@web/core/autocomplete/autocomplete\";\nimport { useEffect } from \"@odoo/owl\";\n\nexport class AutoCompleteWithPages extends AutoComplete {\n    static props = {\n        ...AutoComplete.props,\n        targetDropdown: { type: HTMLElement },\n    };\n    static template = \"website.AutoCompleteWithPages\";\n\n    setup() {\n        super.setup();\n        useEffect(\n            (input, inputRef) => {\n                if (inputRef) {\n                    inputRef.value = input.value;\n                }\n                const targetBlur = this.onInputBlur.bind(this);\n                const targetClick = this._syncInputClick.bind(this);\n                const targetChange = this.onInputChange.bind(this);\n                const targetInput = this._syncInputValue.bind(this);\n                const targetKeydown = this.onInputKeydown.bind(this);\n                const targetFocus = this.onInputFocus.bind(this);\n                input.addEventListener(\"blur\", targetBlur);\n                input.addEventListener(\"click\", targetClick);\n                input.addEventListener(\"change\", targetChange);\n                input.addEventListener(\"input\", targetInput);\n                input.addEventListener(\"keydown\", targetKeydown);\n                input.addEventListener(\"focus\", targetFocus);\n                return () => {\n                    input.removeEventListener(\"blur\", targetBlur);\n                    input.removeEventListener(\"click\", targetClick);\n                    input.removeEventListener(\"change\", targetChange);\n                    input.removeEventListener(\"input\", targetInput);\n                    input.removeEventListener(\"keydown\", targetKeydown);\n                    input.removeEventListener(\"focus\", targetFocus);\n                };\n            },\n            () => [this.targetDropdown, this.inputRef.el]\n        );\n    }\n\n    get targetDropdown() {\n        return this.props.targetDropdown;\n    }\n\n    _syncInputClick(ev) {\n        ev.stopPropagation();\n        this.onInputClick(ev);\n    }\n\n    async _syncInputValue() {\n        if (this.inputRef.el) {\n            this.inputRef.el.value = this.targetDropdown.value;\n            this.onInput();\n        }\n    }\n\n    /**\n     * @override\n     */\n    onInputFocus(ev) {\n        this.targetDropdown.setSelectionRange(0, this.targetDropdown.value.length);\n        this.props.onFocus(ev);\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\nimport { AutoCompleteWithPages } from \"@website/components/autocomplete_with_pages/autocomplete_with_pages\";\n\n// TODO: we probably don't need it anymore after merging html_builder\n// see: https://github.com/odoo/odoo/pull/187091\nexport class UrlAutoComplete extends Component {\n    static props = {\n        options: { type: Object },\n        loadAnchors: { type: Function },\n        targetDropdown: { type: HTMLElement },\n    };\n    static template = \"website.UrlAutoComplete\";\n    static components = { AutoCompleteWithPages };\n\n    setup() {\n        this.inputRef = useChildRef();\n    }\n\n    get dropdownClass() {\n        const classList = [];\n        for (const key in this.props.options?.classes) {\n            classList.push(key, this.props.options.classes[key]);\n        }\n        return classList.join(\" \");\n    }\n\n    get dropdownOptions() {\n        const options = {};\n        if (this.props.options?.position) {\n            options.position = this.props.options?.position;\n        }\n        return options;\n    }\n\n    get sources() {\n        return [\n            {\n                optionSlot: \"option\",\n                options: async (term) => {\n                    const makeItem = (item) => ({\n                        cssClass: \"ui-autocomplete-item\",\n                        label: item.label,\n                        onSelect: this.onSelect.bind(this, item.value),\n                    });\n\n                    if (term[0] === \"#\") {\n                        const anchors = await this.props.loadAnchors(\n                            term,\n                            this.props.options && this.props.options.body\n                        );\n                        return anchors.map((anchor) => makeItem({ label: anchor, value: anchor }));\n                    } else if (term.startsWith(\"http\") || term.length === 0) {\n                        // avoid useless call to /website/get_suggested_links\n                        return [];\n                    }\n                    if (this.props.options.isDestroyed?.()) {\n                        return [];\n                    }\n                    const res = await rpc(\"/website/get_suggested_links\", {\n                        needle: term,\n                        limit: 15,\n                    });\n                    const choices = [];\n                    for (const page of res.matching_pages) {\n                        choices.push(makeItem(page));\n                    }\n                    for (const other of res.others) {\n                        if (other.values.length) {\n                            choices.push({\n                                cssClass: \"ui-autocomplete-category\",\n                                data: { separator: true },\n                                label: other.title,\n                            });\n                            for (const page of other.values) {\n                                choices.push(makeItem(page));\n                            }\n                        }\n                    }\n                    return choices;\n                },\n            },\n        ];\n    }\n\n    onSelect(value) {\n        this.inputRef.value = value;\n        this.props.targetDropdown.value = value;\n        this.props.options.urlChosen?.();\n    }\n\n    onInput({ inputValue }) {\n        this.props.targetDropdown.value = inputValue;\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { cookie } from \"@web/core/browser/cookie\";\n\nimport { markup } from \"@odoo/owl\";\nimport { omit } from \"@web/core/utils/objects\";\nimport { stepUtils } from \"@web_tour/tour_utils\";\n\nexport function addMedia(position = \"right\") {\n    return {\n        trigger: `.modal-content footer .btn-primary`,\n        content: markup(_t(\"<b>Add</b> the selected image.\")),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\nexport function assertCssVariable(variableName, variableValue, trigger = \":iframe body\") {\n    return {\n        isActive: [\"auto\"],\n        content: `Check CSS variable ${variableName}=${variableValue}`,\n        trigger: trigger,\n        run() {\n            const styleValue = getComputedStyle(this.anchor).getPropertyValue(variableName);\n            if (\n                (styleValue && styleValue.trim().replace(/[\"']/g, \"\")) !==\n                variableValue.trim().replace(/[\"']/g, \"\")\n            ) {\n                throw new Error(\n                    `Failed precondition: ${variableName}=${styleValue} (should be ${variableValue})`\n                );\n            }\n        },\n    };\n}\nexport function assertPathName(pathname, trigger) {\n    return {\n        content: `Check if we have been redirected to ${pathname}`,\n        trigger: trigger,\n        async run() {\n            await new Promise((resolve) => {\n                let elapsedTime = 0;\n                const intervalTime = 100;\n                const interval = setInterval(() => {\n                    if (window.location.pathname.startsWith(pathname)) {\n                        clearInterval(interval);\n                        resolve();\n                    }\n                    elapsedTime += intervalTime;\n                    if (elapsedTime >= 5000) {\n                        clearInterval(interval);\n                        console.error(`The pathname ${pathname} has not been found`);\n                    }\n                }, intervalTime);\n            });\n        },\n    };\n}\n\nexport function changeBackground(snippet, position = \"bottom\") {\n    return [\n        {\n            trigger: `.o_customize_tab button[data-action-id=\"replaceBgImage\"]`,\n            content: markup(\n                _t(\n                    \"<b>Customize</b> any block through this menu. Try to change the background image of this block.\"\n                )\n            ),\n            tooltipPosition: position,\n            run: \"click\",\n        },\n    ];\n}\n\nexport function changeBackgroundColor(position = \"bottom\") {\n    return {\n        trigger: \".o_customize_tab .o_we_color_preview\",\n        content: markup(\n            _t(\n                \"<b>Customize</b> any block through this menu. Try to change the background color of this block.\"\n            )\n        ),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\n// TODO: RAHG: This function's trigger is same as above. need to be changed\n// to avoid duplication\nexport function selectColorPalette(position = \"left\") {\n    return {\n        trigger: \".o_customize_tab .o_we_color_preview\",\n        content: markup(_t(`<b>Select</b> a Color Palette.`)),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\nexport function changeColumnSize(position = \"right\") {\n    return {\n        trigger: `.oe_overlay.oe_active .o_handles .o_handle:not(.readonly)`,\n        content: markup(_t(\"<b>Slide</b> this button to change the column size.\")),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\nexport function changeImage(snippet, position = \"bottom\") {\n    return [\n        {\n            trigger: \".o_builder_sidebar_open\",\n        },\n        {\n            trigger: snippet.id ? `#wrapwrap .${snippet.id} img` : snippet,\n            content: markup(\n                _t(\"<b>Double click on an image</b> to change it with one of your choice.\")\n            ),\n            tooltipPosition: position,\n            run: \"dblclick\",\n        },\n    ];\n}\n\n/**\n    wTourUtils.changeOption('HeaderTemplate', '[data-name=\"header_alignment_opt\"]', _t('alignment')),\n    By default, prevents the step from being active if a palette is opened.\n    Set allowPalette to true to select options within a palette.\n*/\nexport function changeOption(\n    blockName,\n    actionId = \"\",\n    optionTooltipLabel = \"\",\n    position = \"bottom\",\n    allowPalette = false\n) {\n    const noPalette = allowPalette\n        ? \"\"\n        : !document.querySelector(\".o_popover .o_font_color_selector\") && \".o_customize_tab\";\n    const option_block = `${noPalette} [data-container-title='${blockName}']`;\n    return {\n        trigger: `${option_block} ${actionId}, ${option_block} [data-action-id=\"${actionId}\"]`,\n        content: markup(\n            _t(\"<b>Click</b> on this option to change the %s of the block.\", optionTooltipLabel)\n        ),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\n/*\n * This function is used when the desired UI control is embedded inside popover\n * (e.g., a dropdown that appears only after clicking a toggle).\n *\n * It constructs two steps:\n *   1. Clicks the dropdown toggle or control to open the popover.\n *   2. Clicks the target element (option) inside the popover.\n *\n * Note: This function assumes that the popover content is available and render\n *       immediately after the first click.\n *\n * @param {string} blockName - The name of the block (e.g., \"Text - Image\").\n * @param {string} optionName - The name of the option (e.g., \"Visibility\").\n * @param {string} elementName - The name of the element to be clicked inside\n *                               the popover (e.g., \"Conditionally\").\n * @param {Boolean} searchNeeded - If the widget is a m2o widget and a search is needed.\n *\n * Example:\n *      ...changeOptionInPopover(\"Text - Image\", \"Visibility\", \"Conditionally\")\n */\nexport function changeOptionInPopover(blockName, optionName, elementName, searchNeeded = false) {\n    const steps = [changeOption(blockName, `[data-label='${optionName}'] .dropdown-toggle`)];\n\n    if (searchNeeded) {\n        steps.push({\n            content: `Inputing ${elementName} in toogle option search`,\n            trigger: `.o_popover input`,\n            run: `edit ${elementName}`,\n        });\n    }\n\n    steps.push(\n        clickOnElement(\n            `${elementName} in the ${optionName} option`,\n            [\n                `.o_popover div.o-dropdown-item:contains(\"${elementName}\")`,\n                `.o_popover span.o-dropdown-item:contains(\"${elementName}\")`,\n                `.o_popover div.o-dropdown-item[title=\"${elementName}\"]`,\n                `.o_popover span.o-dropdown-item[title=\"${elementName}\"]`,\n                `.o_popover ${elementName}`,\n            ].join(\", \")\n        )\n    );\n    return steps;\n}\n\nexport function selectNested(\n    trigger,\n    optionName,\n    altTrigger = null,\n    optionTooltipLabel = \"\",\n    position = \"top\",\n    allowPalette = false\n) {\n    const noPalette = allowPalette\n        ? \"\"\n        : \".o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened))\";\n    const option_block = `${noPalette} we-customizeblock-option[class='snippet-option-${optionName}']`;\n    return {\n        trigger: trigger + (altTrigger ? `, ${option_block} ${altTrigger}` : \"\"),\n        content: markup(_t(\"<b>Select</b> a %s.\", optionTooltipLabel)),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\nexport function changePaddingSize(direction) {\n    let paddingDirection = \"n\";\n    let position = \"top\";\n    if (direction === \"bottom\") {\n        paddingDirection = \"s\";\n        position = \"bottom\";\n    }\n    return {\n        trigger: `.oe_overlay.oe_active .o_handle.${paddingDirection}`,\n        content: markup(_t(\"<b>Slide</b> this button to change the %s padding\", direction)),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\n/**\n * Checks if an element is visible on the screen, i.e., not masked by another\n * element.\n *\n * @param {String} elementSelector The selector of the element to be checked.\n * @returns {Object} The steps required to check if the element is visible.\n */\nexport function checkIfVisibleOnScreen(elementSelector) {\n    return {\n        content: \"Check if the element is visible on screen\",\n        trigger: `${elementSelector}`,\n        run() {\n            const boundingRect = this.anchor.getBoundingClientRect();\n            const centerX = boundingRect.left + boundingRect.width / 2;\n            const centerY = boundingRect.top + boundingRect.height / 2;\n            const iframeDocument = document.querySelector(\n                \".o_website_preview iframe\"\n            ).contentDocument;\n            const el = iframeDocument.elementFromPoint(centerX, centerY);\n            if (!this.anchor.contains(el)) {\n                console.error(\"The element is not visible on screen\");\n            }\n        },\n    };\n}\n\n/**\n * Simple click on an element in the page.\n * @param {*} elementName\n * @param {*} selector\n */\nexport function clickOnElement(elementName, selector) {\n    return {\n        content: `Clicking on the ${elementName}`,\n        trigger: selector,\n        run: \"click\",\n    };\n}\n\n/**\n * Click on the top right edit button and wait for the edit mode\n *\n * @param {string} position Where the purple arrow will show up\n */\nexport function clickOnEditAndWaitEditMode(position = \"bottom\") {\n    return [\n        {\n            content: markup(_t(\"<b>Click Edit</b> to start designing your homepage.\")),\n            trigger: \"body .o_menu_systray .o_menu_systray_item.o_edit_website_container button\",\n            tooltipPosition: position,\n            run: \"click\",\n        },\n        {\n            content: \"Check that we are in edit mode\",\n            trigger: \".o_builder_sidebar_open\",\n        },\n    ];\n}\n\n/**\n * Click on the top right edit dropdown, then click on the edit dropdown item\n * and wait for the edit mode\n *\n * @param {string} position Where the purple arrow will show up\n */\nexport function clickOnEditAndWaitEditModeInTranslatedPage(position = \"bottom\") {\n    return [\n        {\n            content: markup(_t(\"<b>Click Edit</b> dropdown\")),\n            trigger: \"body .o_menu_systray button:contains('Edit')\",\n            tooltipPosition: position,\n            run: \"click\",\n        },\n        {\n            content: markup(_t(\"<b>Click Edit</b> to start designing your homepage.\")),\n            trigger: \".o_edit_website_dropdown_item\",\n            tooltipPosition: position,\n            run: \"click\",\n        },\n        {\n            content: \"Check that we are in edit mode\",\n            trigger: \".o_builder_sidebar_open\",\n        },\n    ];\n}\n\n/**\n * Simple click on a snippet in the edition area\n * @param {*} snippet\n * @param {*} position\n */\nexport function clickOnSnippet(snippet, position = \"bottom\") {\n    const trigger = snippet.id ? `#wrapwrap .${snippet.id}` : snippet;\n    return [\n        {\n            trigger: \".o-website-builder_sidebar\",\n            noPrepend: true,\n        },\n        {\n            trigger: `:iframe ${trigger}`,\n            content: markup(_t(\"<b>Click on a snippet</b> to access its options menu.\")),\n            tooltipPosition: position,\n            run: \"click\",\n        },\n    ];\n}\n\nexport function clickOnSave(position = \"bottom\", timeout = 50000, withContains = true) {\n    return [\n        {\n            trigger: \".o-snippets-menu:not(:has(.o_we_ongoing_insertion))\",\n        },\n        {\n            trigger: \"body:not(:has(.o_dialog))\",\n            noPrepend: true,\n        },\n        {\n            trigger: withContains\n                ? \"button[data-action=save]:enabled:contains(save)\"\n                : \"button[data-action=save]:enabled\",\n            content: markup(_t(\"Good job! It's time to <b>Save</b> your work.\")),\n            tooltipPosition: position,\n            run: \"click\",\n            timeout,\n        },\n        {\n            trigger: \"body:not(.o_builder_open)\",\n            noPrepend: true,\n            timeout,\n        },\n        stepUtils.waitIframeIsReady(),\n    ];\n}\n\n/**\n * Click on a snippet's text to modify its content\n * @param {*} snippet\n * @param {*} element Target the element which should be rewrite\n * @param {*} position\n */\nexport function clickOnText(snippet, element, position = \"bottom\") {\n    return [\n        {\n            trigger: \":iframe body .odoo-editor-editable\",\n        },\n        {\n            trigger: snippet.id ? `:iframe #wrapwrap .${snippet.id} ${element}` : snippet,\n            content: markup(_t(\"<b>Click on a text</b> to start editing it.\")),\n            tooltipPosition: position,\n            run: \"click\",\n        },\n        {\n            trigger: \"#customize-tab.active\",\n        },\n    ];\n}\n\n/**\n * Selects a category or an inner snippet from the snippets menu and insert it\n * in the page.\n * @param {*} snippet contain the id and the name of the targeted snippet. If it\n * contains a group it means that the snippet is shown in the \"add snippets\"\n * dialog.\n * @param {*} position Where the purple arrow will show up\n */\nexport function insertSnippet(snippet, { position = \"bottom\", ignoreLoading = false } = {}) {\n    const blockEl = snippet.groupName || snippet.name;\n    const insertSnippetSteps = [\n        {\n            trigger: \".o_builder_sidebar_open\",\n            noPrepend: true,\n        },\n    ];\n    const snippetIDSelector = snippet.id\n        ? `[data-snippet-id=\"${snippet.id}\"]`\n        : `[data-snippet-id^=\"${snippet.customID}_\"]`;\n    if (snippet.groupName) {\n        insertSnippetSteps.push(\n            {\n                content: markup(_t(\"Click on the <b>%s</b> category.\", blockEl)),\n                trigger: `.o_block_tab:not(.o_we_ongoing_insertion) #snippet_groups .o_snippet[name=\"${blockEl}\"].o_draggable .o_snippet_thumbnail_area`,\n                tooltipPosition: position,\n                run: \"click\",\n            },\n            {\n                content: markup(_t(\"Click on the <b>%s</b> building block.\", snippet.name)),\n                // FIXME `:not(.d-none)` should obviously not be needed but it seems\n                // currently needed when using a tour in user/interactive mode.\n                trigger: `.modal .show:iframe .o_snippet_preview_wrap${snippetIDSelector}:not(.d-none)`,\n                noPrepend: true,\n                tooltipPosition: \"top\",\n                run: \"click\",\n            }\n        );\n    } else {\n        insertSnippetSteps.push({\n            content: markup(\n                _t(\"Drag the <b>%s</b> block and drop it at the bottom of the page.\", blockEl)\n            ),\n            trigger: `.o_block_tab:not(.o_we_ongoing_insertion) #snippet_content .o_snippet[name=\"${blockEl}\"].o_draggable .o_snippet_thumbnail`,\n            tooltipPosition: position,\n            run: \"drag_and_drop :iframe #wrapwrap > footer\",\n        });\n    }\n\n    if (!ignoreLoading) {\n        insertSnippetSteps.push({\n            trigger: \":iframe:not(:has(.o_loading_screen))\",\n        });\n    }\n\n    return insertSnippetSteps;\n}\n\nexport function goBackToBlocks(position = \"bottom\") {\n    return {\n        trigger: \"button[data-name='blocks']\",\n        content: _t(\"Click here to go back to block tab.\"),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\nexport function goToTheme(position = \"bottom\") {\n    return [\n        {\n            trigger: \".o-website-builder_sidebar\",\n        },\n        {\n            trigger: \"button[data-name='theme']\",\n            content: _t(\"Go to the Theme tab\"),\n            tooltipPosition: position,\n            run: \"click\",\n        },\n        {\n            content: \"Check that the theme tab is active\",\n            trigger: \".o-tab-content .options-container [data-action-id='switchTheme']\",\n        },\n    ];\n}\n\nexport function selectHeader(position = \"bottom\") {\n    return {\n        trigger: `:iframe header#top`,\n        content: markup(_t(`<b>Click</b> on this header to configure it.`)),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\nexport function selectSnippetColumn(snippet, index = 0, position = \"bottom\") {\n    return {\n        trigger: `:iframe #wrapwrap .${snippet.id} .row div[class*=\"col-lg-\"]:eq(${index})`,\n        content: markup(_t(\"<b>Click</b> on this column to access its options.\")),\n        tooltipPosition: position,\n        run: \"click\",\n    };\n}\n\nexport function prepend_trigger(steps, prepend_text = \"\") {\n    for (const step of steps) {\n        if (!step.noPrepend && prepend_text) {\n            step.trigger = prepend_text + step.trigger;\n        }\n    }\n    return steps;\n}\n\nexport function getClientActionUrl(path, edition) {\n    let url = `/odoo/action-website.website_preview`;\n    if (path) {\n        url += `?path=${encodeURIComponent(path)}`;\n    }\n    if (edition) {\n        url += `${path ? \"&\" : \"?\"}enable_editor=1`;\n    }\n    return url;\n}\n\nexport function clickOnExtraMenuItem(stepOptions, backend = false) {\n    return Object.assign(\n        {\n            content: \"Click on the extra menu dropdown toggle if it is there\",\n            trigger: `${backend ? \":iframe\" : \"\"} .top_menu`,\n            async run(actions) {\n                // Note: the button might not exist (it only appear if there is\n                // many menu items).\n                const extraMenuButton = this.anchor.querySelector(\".o_extra_menu_items a.nav-link\");\n                // Don't click on the extra menu button if it's already visible.\n                if (extraMenuButton && !extraMenuButton.classList.contains(\"show\")) {\n                    await actions.click(extraMenuButton);\n                }\n            },\n        },\n        stepOptions\n    );\n}\n\n/**\n * Registers a tour that will go in the website client action.\n *\n * @param {string} name The tour's name\n * @param {object} options The tour options\n * @param {string} options.url The page to edit\n * @param {boolean} [options.edition] If the tour starts in edit mode\n * @param {() => TourStep[]} steps The steps of the tour. Has to be a function to avoid direct interpolation of steps.\n */\nexport function registerWebsitePreviewTour(name, options, steps) {\n    if (typeof steps !== \"function\") {\n        throw new Error(`tour.steps has to be a function that returns TourStep[]`);\n    }\n    registry.category(\"web_tour.tours\").remove(name);\n    return registry.category(\"web_tour.tours\").add(name, {\n        ...omit(options, \"edition\"),\n        url: getClientActionUrl(options.url, !!options.edition),\n        steps: () => {\n            const tourSteps = [...steps()];\n            // Note: for both non edit mode and edit mode, we set a high timeout for the\n            // first step. Indeed loading both the backend and the frontend (in the\n            // iframe) and potentially starting the edit mode can take a long time in\n            // automatic tests. We'll try and decrease the need for this high timeout\n            // of course.\n            if (options.edition) {\n                tourSteps.unshift({\n                    content: \"Wait for the edit mode to be started\",\n                    trigger: \".o_builder_sidebar_open\",\n                    timeout: 30000,\n                });\n            } else {\n                tourSteps[0].timeout = 20000;\n            }\n            return tourSteps.map((step) => {\n                delete step.noPrepend;\n                return step;\n            });\n        },\n    });\n}\n\nexport function registerThemeHomepageTour(name, steps) {\n    if (typeof steps !== \"function\") {\n        throw new Error(`tour.steps has to be a function that returns TourStep[]`);\n    }\n    return registerWebsitePreviewTour(\n        \"homepage\", // it overrides the community tour with the associated theme tour\n        {\n            url: \"/\",\n        },\n        () => [\n            ...clickOnEditAndWaitEditMode(),\n            // FIXME(?) this should probably reuse the prepend_trigger function\n            // so that we do check that we are really on the homepage.\n            ...steps(),\n            ...goToTheme(),\n            ...clickOnSave(),\n        ]\n    );\n}\n\nexport function registerBackendAndFrontendTour(name, options, steps) {\n    if (typeof steps !== \"function\") {\n        throw new Error(`tour.steps has to be a function that returns TourStep[]`);\n    }\n    if (window.location.pathname === \"/odoo\") {\n        return registerWebsitePreviewTour(name, options, () => {\n            const newSteps = [];\n            for (const step of steps()) {\n                const newStep = Object.assign({}, step);\n                newStep.trigger = `:iframe ${step.trigger}`;\n                newSteps.push(newStep);\n            }\n            return newSteps;\n        });\n    }\n\n    return registry.category(\"web_tour.tours\").add(name, {\n        url: options.url,\n        steps: () => steps(),\n    });\n}\n\n/**\n * Switches to a different website by clicking on the website switcher.\n *\n * @param {number} websiteId - The ID of the website to switch to.\n * @param {string} websiteName - The name of the website to switch to.\n * @returns {Array} - The steps required to perform the website switch.\n */\nexport function switchWebsite(websiteId, websiteName) {\n    return [\n        {\n            content: `Click on the website switch to switch to website '${websiteName}'`,\n            trigger: \".o_website_switcher_container button\",\n            run: \"click\",\n        },\n        {\n            trigger: `:iframe html:not([data-website-id=\"${websiteId}\"])`,\n        },\n        {\n            content: `Switch to website '${websiteName}'`,\n            trigger: `.o-dropdown--menu .dropdown-item[data-website-id=\"${websiteId}\"]:contains(\"${websiteName}\")`,\n            run: \"click\",\n        },\n        {\n            content: \"Wait for the iframe to be loaded\",\n            // The page reload generates assets for the new website, it may take\n            // some time\n            timeout: 20000,\n            trigger: `:iframe html[data-website-id=\"${websiteId}\"]`,\n        },\n    ];\n}\n\n/**\n * Switches to a different website by clicking on the website switcher.\n * This function can only be used during test tours as it requires\n * specific cookies to properly function.\n *\n * @param {string} websiteName - The name of the website to switch to.\n * @returns {Array} - The steps required to perform the website switch.\n */\nexport function testSwitchWebsite(websiteName) {\n    const websiteIdMapping = JSON.parse(cookie.get(\"websiteIdMapping\") || \"{}\");\n    const websiteId = websiteIdMapping[websiteName];\n    return switchWebsite(websiteId, websiteName);\n}\n\n/**\n * Toggles the mobile preview on or off.\n *\n * @param {Boolean} toggleOn true to toggle the mobile preview on, false to\n *     toggle it off.\n * @returns {Array}\n */\nexport function toggleMobilePreview(toggleOn) {\n    const onOrOff = toggleOn ? \"on\" : \"off\";\n    const mobileOnSelector = \".o_is_mobile\";\n    const mobileOffSelector = \":not(.o_is_mobile)\";\n    return [\n        {\n            trigger: `div.o_website_preview${toggleOn ? mobileOffSelector : mobileOnSelector}`,\n        },\n        {\n            content: `Toggle the mobile preview ${onOrOff}`,\n            trigger: \".o-snippets-top-actions [data-action='mobile']\",\n            run: \"click\",\n        },\n        {\n            content: `Check that the mobile preview is ${onOrOff}`,\n            trigger: `div.o_website_preview${toggleOn ? mobileOnSelector : mobileOffSelector}`,\n        },\n    ];\n}\n\n/**\n * Opens the link popup for the specified link element.\n *\n * @param {string} triggerSelector - Selector for the link element.\n * @param {string} [linkName=\"\"] - Name of the link.\n * @param {number} [focusNodeIndex=0] - Index of the child node to focus inside\n *                                      the link element.\n * @returns {TourStep[]} The tour steps that opens the link popup.\n */\nexport function openLinkPopup(\n    triggerSelector,\n    linkName = \"\",\n    focusNodeIndex = 0,\n    triggerClick = false\n) {\n    return [\n        {\n            content: `Open '${linkName}' link popup`,\n            trigger: triggerSelector,\n            async run(actions) {\n                if (triggerClick) {\n                    actions.click();\n                }\n                const el = this.anchor;\n                const sel = el.ownerDocument.getSelection();\n                sel.collapse(el.childNodes[focusNodeIndex], 1);\n                el.focus();\n            },\n        },\n        {\n            content: \"Check if the link popover opened\",\n            trigger: \".o-we-linkpopover\",\n        },\n    ];\n}\n\n/**\n * Selects all the text of an element.\n * @param {*} elementName\n * @param {*} selector\n */\nexport function selectFullText(elementName, selector) {\n    return {\n        content: `Select all the text of the ${elementName}`,\n        trigger: `:iframe ${selector}`,\n        async run(actions) {\n            await actions.click();\n            const range = document.createRange();\n            const selection = this.anchor.ownerDocument.getSelection();\n            range.selectNodeContents(this.anchor);\n            selection.removeAllRanges();\n            selection.addRange(range);\n        },\n    };\n}\n\n/**\n * Click button from the toolbar, if expand is true, it will\n * first expand the toolbar.\n * @param {string} elementName\n * @param {string} selector\n * @param {string} button\n * @param {boolean} expand - Whether to expand the toolbar for more buttons.\n * @returns {Array} The steps to click the toolbar button.\n */\nexport function clickToolbarButton(elementName, selector, button, expand = false) {\n    const steps = [\n        selectFullText(`${elementName}`, selector),\n        {\n            content: `Click on the ${button} from toolbar`,\n            trigger: `.o-we-toolbar button[title=\"${button}\"], .o-we-toolbar button[name=\"${button}\"]`,\n            run: \"click\",\n        },\n    ];\n    if (expand) {\n        steps.splice(1, 0, {\n            content: \"Expand the toolbar for more buttons\",\n            trigger: \".o-we-toolbar button[name='expand_toolbar']\",\n            run: \"click\",\n        });\n    }\n    return steps;\n}\n", "import publicRootData from \"@web/legacy/js/public/public_root\";\nimport \"@website/libs/zoomodoo/zoomodoo\";\nimport { pick } from \"@web/core/utils/objects\";\n\nexport const WebsiteRoot = publicRootData.PublicRoot.extend({\n    events: Object.assign({}, publicRootData.PublicRoot.prototype.events || {}, {\n        \"click .js_change_lang\": \"_onLangChangeClick\",\n        \"click .js_publish_management .js_publish_btn\": \"_onPublishBtnClick\",\n        \"shown.bs.modal\": \"_onModalShown\",\n    }),\n    custom_events: Object.assign({}, publicRootData.PublicRoot.prototype.custom_events || {}, {\n        gmap_api_request: \"_onGMapAPIRequest\",\n        gmap_api_key_request: \"_onGMapAPIKeyRequest\",\n        ready_to_clean_for_save: \"_onWidgetsStopRequest\",\n        seo_object_request: \"_onSeoObjectRequest\",\n        will_remove_snippet: \"_onWidgetsStopRequest\",\n    }),\n\n    /**\n     * @override\n     */\n    init() {\n        this.isFullscreen = false;\n        this.notification = this.bindService(\"notification\");\n        this.orm = this.bindService(\"orm\");\n        this.website_map = this.bindService(\"website_map\");\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        // Enable magnify on zommable img\n        this.$(\".zoomable img[data-zoom]\").zoomOdoo();\n\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _getContext: function (context) {\n        var html = document.documentElement;\n        return Object.assign(\n            {\n                website_id: html.getAttribute(\"data-website-id\") | 0,\n            },\n            this._super.apply(this, arguments)\n        );\n    },\n    /**\n     * @override\n     */\n    _getExtraContext: function (context) {\n        var html = document.documentElement;\n        return Object.assign(\n            {\n                editable: !!(html.dataset.editable || $(\"[data-oe-model]\").length), // temporary hack, this should be done in python\n                translatable: !!html.dataset.translatable,\n                edit_translations: !!html.dataset.edit_translations,\n            },\n            this._super.apply(this, arguments)\n        );\n    },\n    /**\n     * @override\n     */\n    _getPublicWidgetsRegistry: function (options) {\n        var registry = this._super.apply(this, arguments);\n        if (options.editableMode) {\n            const toPick = Object.keys(registry).filter((key) => {\n                const PublicWidget = registry[key];\n                return !PublicWidget.prototype.disabledInEditableMode;\n            });\n            return pick(registry, ...toPick);\n        }\n        return registry;\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _onWidgetsStartRequest: function (ev) {\n        ev.data.options = Object.assign({}, ev.data.options || {});\n        ev.data.options.editableMode = ev.data.editableMode;\n        this._super.apply(this, arguments);\n    },\n    /**\n     * @todo review\n     * @private\n     */\n    _onLangChangeClick: function (ev) {\n        ev.preventDefault();\n        // In edit mode, the client action redirects the iframe to the correct\n        // location with the chosen language.\n        if (document.body.classList.contains(\"editor_enable\")) {\n            return;\n        }\n        var $target = $(ev.currentTarget);\n        // retrieve the hash before the redirect\n        var redirect = {\n            lang: encodeURIComponent($target.data(\"url_code\")),\n            url: encodeURIComponent(\n                $target.attr(\"href\").replace(/[&?]edit_translations[^&?]+/, \"\")\n            ),\n            hash: encodeURIComponent(window.location.hash),\n        };\n        window.location.href = `/website/lang/${redirect.lang}?r=${redirect.url}${redirect.hash}`;\n    },\n    /**\n     * @private\n     * @param {OdooEvent} ev\n     */\n    async _onGMapAPIRequest(ev) {\n        ev.stopPropagation();\n        const apiKey = await this.website_map.loadGMapAPI(ev.data.editableMode, ev.data.refetch);\n        ev.data.onSuccess(apiKey);\n    },\n    /**\n     * @private\n     * @param {OdooEvent} ev\n     */\n    async _onGMapAPIKeyRequest(ev) {\n        ev.stopPropagation();\n        const apiKey = await this.website_map.getGMapAPIKey(ev.data.refetch);\n        ev.data.onSuccess(apiKey);\n    },\n    /**\n    /**\n     * Checks information about the page SEO object.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onSeoObjectRequest: function (ev) {\n        var res = this._unslugHtmlDataObject(\"seo-object\");\n        ev.data.callback(res);\n    },\n    /**\n     * Returns a model/id object constructed from html data attribute.\n     *\n     * @private\n     * @param {string} dataAttr\n     * @returns {Object} an object with 2 keys: model and id, or null\n     * if not found\n     */\n    _unslugHtmlDataObject: function (dataAttr) {\n        var repr = $(\"html\").data(dataAttr);\n        var match = repr && repr.match(/(.+)\\((-?\\d+),(.*)\\)/);\n        if (!match) {\n            return null;\n        }\n        return {\n            model: match[1],\n            id: match[2] | 0,\n        };\n    },\n    /**\n     * @todo review\n     * @private\n     */\n    _onPublishBtnClick: function (ev) {\n        ev.preventDefault();\n        if (document.body.classList.contains(\"editor_enable\")) {\n            return;\n        }\n\n        const publishEl = ev.currentTarget.closest(\".js_publish_management\");\n        this.orm\n            .call(publishEl.dataset.object, \"website_publish_button\", [\n                [parseInt(publishEl.dataset.id, 10)],\n            ])\n            .then(function (result) {\n                publishEl.classList.toggle(\"css_published\", result);\n                publishEl.classList.toggle(\"css_unpublished\", !result);\n                const itemEl = publishEl.closest(\"[data-publish]\");\n                if (itemEl) {\n                    itemEl.dataset.publish = result ? \"on\" : \"off\";\n                }\n            });\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onModalShown: function (ev) {\n        $(ev.target).addClass(\"modal_shown\");\n    },\n});\n\nexport default {\n    WebsiteRoot: WebsiteRoot,\n};\n", "/**\n * Tweaks the website rendering so that the old browsers correctly render the\n * content too.\n */\n\n// Check if flex is supported and add the info as an attribute of the HTML\n// element so that css selectors can match it (only if not supported)\nvar htmlStyle = document.documentElement.style;\nvar isFlexSupported =\n    \"flexWrap\" in htmlStyle || \"WebkitFlexWrap\" in htmlStyle || \"msFlexWrap\" in htmlStyle;\nif (!isFlexSupported) {\n    document.documentElement.setAttribute(\"data-no-flex\", \"\");\n}\n\nexport default {\n    isFlexSupported: isFlexSupported,\n};\n", "/**\n * Provides a way to start JS code for snippets' initialization and animations.\n */\n\nimport publicWidget from \"@web/legacy/js/public/public_widget\";\n\n/**\n * Add the notion of edit mode to public widgets.\n */\npublicWidget.Widget.include({\n    /**\n     * Indicates if the widget should not be instantiated in edit. The default\n     * is true, indeed most (all?) defined widgets only want to initialize\n     * events and states which should not be active in edit mode (this is\n     * especially true for non-website widgets).\n     *\n     * @type {boolean}\n     */\n    disabledInEditableMode: true,\n    /**\n     * Acts as @see Widget.events except that the events are only binded if the\n     * Widget instance is instanciated in edit mode. The property is not\n     * considered if @see disabledInEditableMode is false.\n     */\n    edit_events: null,\n    /**\n     * Acts as @see Widget.events except that the events are only binded if the\n     * Widget instance is instanciated in readonly mode. The property only\n     * makes sense if @see disabledInEditableMode is false, you should simply\n     * use @see Widget.events otherwise.\n     */\n    read_events: null,\n\n    /**\n     * Initializes the events that will need to be binded according to the\n     * given mode.\n     *\n     * @constructor\n     * @param {Object} parent\n     * @param {Object} [options]\n     * @param {boolean} [options.editableMode=false]\n     *        true if the page is in edition mode\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n\n        this.editableMode = this.options.editableMode || false;\n        var extraEvents = this.editableMode ? this.edit_events : this.read_events;\n        if (extraEvents) {\n            this.events = Object.assign({}, this.events || {}, extraEvents);\n        }\n    },\n});\n\n//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n\nvar registry = publicWidget.registry;\n\n// TODO Let's keep this here for now: will have to move an edit mode related\n// location.\n// FIXME temporary hack: during edit mode, the carousel crashes sometimes when\n// we hover option during a carousel cycle. This patches Bootstrap to prevent\n// the crash.\nconst baseSelectorEngineFind = window.SelectorEngine.find;\nwindow.SelectorEngine.find = function (...args) {\n    try {\n        return baseSelectorEngineFind.call(this, ...args);\n    } catch {\n        return [document.createElement(\"div\")];\n    }\n};\n\nexport default {\n    Widget: publicWidget.Widget,\n    registry: registry,\n};\n", "//\n// This file is meant to regroup your javascript code. You can either copy/past\n// any code that should be executed on each page loading or write your own\n// taking advantage of the Odoo framework to create new behaviors or modify\n// existing ones. For example, doing this will greet any visitor with a 'Hello,\n// world !' message in a popup:\n//\n/*\nimport { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog';\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nclass HelloWorldPopup extends Interaction {\n    static selector = \"#wrapwrap\";\n\n    start() {\n        this.services.dialog.add(ConfirmationDialog, { body: \"hello world\"});\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.hello_world_popup\", HelloWorldPopup);\n*/\n", "import { cookie } from \"@web/core/browser/cookie\";\nimport { patch } from \"@web/core/utils/patch\";\n\npatch(cookie, {\n    isAllowedCookie(type) {\n        if (type === \"optional\") {\n            if (!document.getElementById(\"cookies-consent-essential\")) {\n                // Cookies bar is disabled on this website.\n                return true;\n            }\n            const consents = JSON.parse(cookie.get(\"website_cookies_bar\") || \"{}\");\n\n            // pre-16.0 compatibility, `website_cookies_bar` was `\"true\"`.\n            // In that case we delete that cookie and let the user choose again.\n            if (typeof consents !== \"object\") {\n                cookie.delete(\"website_cookies_bar\");\n                return false;\n            }\n\n            if (\"optional\" in consents) {\n                return consents[\"optional\"];\n            }\n            return false;\n        }\n        return true;\n    },\n    set(key, value, ttl, type = \"required\") {\n        super.set(key, value, this.isAllowedCookie(type) ? ttl : 0);\n    },\n});\n", "import { isVisible } from \"@web/core/utils/ui\";\n\n//TODO: Delete higlight function (duplicate whith highlight_utils) when deleting snippets options\n// SVG generator: contains all information needed to draw highlight SVGs\n// according to text dimensions, highlight style,...\nconst _textHighlightFactory = {\n    underline: (targetEl) => drawPath(targetEl, { mode: \"line\" }),\n    freehand_1: (targetEl) => {\n        const template = (w, h) => [\n            `M 0,${h * 1.1} C ${w / 8},${h * 1.05} ${w / 4},${h} ${w},${h}`,\n        ];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    freehand_2: (targetEl) => {\n        const template = (w, h) => [\n            `M181.27 13.873c-.451-1.976-.993-3.421-1.072-4.9-.125-2.214-.61-4.856.384-6.539.756-1.287 3.636-2.055 5.443-1.852 3.455.395 7.001 1.231` +\n                ` 10.14 2.676 1.728.802 3.174 3.06 3.817 4.98.237.712-1.953 2.824-3.399 3.4-2.766 1.095-5.748 1.75-8.706 2.179-2.394.339-4.879.068-6.584.068l-.023-.012ZM8.416 3.90` +\n                `2c3.862.26 7.78.249 11.574.926 1.65.294 3.027 2.033 4.54 3.117-1.095 1.186-1.987 2.982-3.343 3.456a67.118 67.118 0 0 1-11.19 2.823c-3.253.53-6.494-.339-8.617-2.98` +\n                `1C.364 9.978-.302 7.686.138 6.263c.361-1.152 2.54-2 4.077-2.44 1.287-.372 2.789.046 4.2.102v-.023Zm154.267 9.983c-4.291-.305-8.153-1.58-9.915-5.623-.745-1.694-.39` +\n                `5-4.382.474-6.121 1.073-2.168 3.512-1.965 5.613-1.005 2.541 1.174 5.251 2.157 7.509 3.76 1.502 1.073 3.557 3.445 3.207 4.574-.519 1.694-2.857 2.913-4.562 4.133-.5` +\n                `76.406-1.592.203-2.326.282ZM72.58 17.42c-2.733-1.807-5.307-3.004-7.137-4.913-.892-.925-.892-3.376-.361-4.776.407-1.05 2.304-2.112 3.546-2.135 3.602-.056 7.238.215` +\n                ` 10.818.723 3.828.542 5.15 4.1 2.213 6.539-2.439 2.021-5.77 2.958-9.079 4.562Zm30.795-.802c-2.507-1.536-5.228-2.823-7.397-4.743-.925-.813-1.377-3.297-.813-4.359.6` +\n                `78-1.265 2.677-2.507 4.11-2.518 3.016-.023 6.155.418 9.001 1.389 1.412.485 3.173 2.552 3.185 3.907 0 1.57-1.423 3.557-2.801 4.619-1.152.892-3.139.711-4.743 1.005-` +\n                `.181.226-.35.463-.531.689l-.011.01Zm-59.704-1.457c-2.066-1.163-4.788-2.224-6.82-4.054-.915-.824-1.04-3.478-.407-4.765.486-.983 2.722-1.559 4.156-1.502 2.676.101 5` +\n                `.398.542 7.95 1.332 1.457.452 3.523 1.75 3.681 2.891.18 1.31-1.13 3.309-2.383 4.201-1.411 1.005-3.466 1.118-6.188 1.886l.011.011Zm88.489-1.863c-2.643-1.48-5.567-2` +\n                `.62-7.803-4.574-1.005-.88-1.31-3.692-.667-5.002.509-1.04 2.982-1.615 4.529-1.513 2.032.135 4.054 1.027 6.007 1.772 2.485.95 5.026 2.236 4.382 5.455-.644 3.15-3.49` +\n                ` 2.947-5.963 3.004-.169.293-.327.575-.496.87l.011-.012Z`,\n        ];\n        return drawPath(targetEl, {\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 18,\n            position: \"bottom\",\n        });\n    },\n    freehand_3: (targetEl) => {\n        const template = (w, h) => [\n            `M189.705 18.285c-3.99.994-7.968 2.015-11.958 2.972-1.415.344-2.926 1.008-4.278.727-6.305-1.327-12.568-3.036-18.874-4.376-1.995-.42-4.2` +\n                `46-.701-6.133-.038-5.867 2.067-11.54 2.386-17.374-.242-1.491-.676-3.56-.421-5.125.217-5.523 2.22-10.789 3.597-16.494.127-1.64-.995-4.675-.038-6.584 1.148-6.102 3.` +\n                `789-12.01 4.414-18.198.434-.998-.638-2.681-.638-3.754-.115-6.852 3.355-13.404 2.858-20.043-1.008-1.5-.867-4.02-.6-5.608.307-7.528 4.35-14.842 5.702-22.07-.638-2.1` +\n                `44-1.875-3.71-.37-5.394 1.046-4.622 3.89-9.565 6.327-15.367 4.286C6.338 20.989.505 13.067.022 5.949-.085 4.38.194 1.753.955 1.332 2.253.617 4.537.553 5.588 1.51 7` +\n                `.55 3.27 9.18 5.77 10.52 8.296c2.82 5.269 4.15 5.766 8.504 2.156 1.555-1.288 2.992-2.768 4.396-4.286 4.022-4.311 7.143-4.465 11.26-.472 7.068 6.837 8.226 7.067 15` +\n                `.979 1.314 3.721-2.755 7.206-2.653 10.627.128 4.987 4.056 9.791 4.49 14.853.191 2.702-2.296 5.78-2.296 8.45.115 4.29 3.89 8.45 3.33 12.719.166.847-.638 1.705-1.26` +\n                `3 2.552-1.914 3.035-2.309 6.048-2.5 9.019.166 3.453 3.087 7.12 3.15 10.616.472 4.107-3.138 7.85-3.342 12.16-.306 3.668 2.59 7.83 1.964 11.594-.255 3.935-2.322 7.6` +\n                `67-2.488 11.409.408.365.28.794.612 1.213.65 6.799.549 13.522 3.394 20.428.779 1.887-.715 3.914-1.034 5.899-1.148 3.313-.192 6.659-.358 9.941 0 1.993.23 4.354.905 ` +\n                `5.737 2.436 1.308 1.429 2.113 4.235 2.123 6.442.022 3.023-2.424 3.431-4.472 3.597-1.887.153-3.796.038-5.695.038-.053-.216-.106-.446-.16-.663l.032-.025Z`,\n        ];\n        return drawPath(targetEl, {\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 24,\n            position: \"bottom\",\n        });\n    },\n    double: (targetEl) => {\n        const template = (w, h) => [`M 0,${h * 0.9} h ${w}`, `M 0,${h * 1.1} h ${w}`];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    wavy: (targetEl) => {\n        const template = (w, h) => [\n            `c ${w / 4},0 ${w / 4},-${h / 2} ${w / 2},-${h / 2}` +\n                `c ${w / 4},0 ${w / 4},${h / 2} ${w / 2},${h / 2}`,\n        ];\n        return drawPath(targetEl, { mode: \"pattern\", template });\n    },\n    circle_1: (targetEl) => {\n        const template = (w, h) => [\n            `M ${w / 2.88},${h / 1.1} C ${w / 1.1},${h / 1.05} ${w * 1.05},${h / 1.1} ${\n                w * 1.023\n            },${h / 2.32}` +\n                `C ${w}, ${h / 14.6} ${w / 1.411},0 ${w / 2},0 S -2,${h / 14.6} -2,${h / 2.2}` +\n                `S ${w / 4.24},${h} ${w / 1.36},${h * 1.04}`,\n        ];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    circle_2: (targetEl) => {\n        const template = (w, h) => [\n            `M112.58 21.164h18.516c-.478-.176-1.722-.64-2.967-1.105.101-.401.214-.803.315-1.192 12.255 2.912 24.561 5.573 36.716 8.823 5.896 1.582 ` +\n                `11.628 3.967 17.171 6.527 10.433 4.832 14.418 14.22 16.479 24.739.377 1.92.566 3.878.83 5.823 2.212 15.94-5.858 23.986-21.595 33.813-.993.615-2.288.79-3.181 1.494` +\n                `-14.229 11.308-31.412 14.32-48.608 17.107-29.01 4.694-57.431 2.209-84.91-8.372-8.145-3.138-16.164-6.853-23.706-11.22C6.176 90.986 1.16 80.053.193 67.25c-1.798-23.` +\n                `809 9.025-42.485 30.356-53.304C44.678 6.793 59.8 3.367 75.45 2.375 90.583 1.42 105.793.379 120.927.78c16.089.427 32.041 3.05 46.911 9.84 2.074.941 3.67 2.912 4.91` +\n                `5 5.083-9.73-1.443-19.433-2.987-29.175-4.305-4.89-.665-9.842-1.067-14.77-1.33-23.82-1.28-47.376.514-70.391 7.003a133.771 133.771 0 0 0-22.639 8.648c-17.9 8.786-27` +\n                `.616 26.935-25.567 46.364.666 6.263 3.507 11.133 9.05 14.308 26.862 15.401 55.748 21.965 86.645 19.819 15.561-1.08 31.01-2.787 45.767-8.284 11.099-4.142 21.658-9.` +\n                `25 30.595-17.195 9.779-8.698 11.715-18.55 5.669-30.249-1.131-2.196-3.256-4.079-5.33-5.56-7.981-5.736-17.773-7.48-26.459-11.534-13.249-6.175-27.541-6.916-41.343-10` +\n                `.167-.817-.188-1.571-.64-2.35-.966.037-.364.088-.728.125-1.092Z`,\n        ];\n        return drawPath(targetEl, { mode: \"fill\", template, SVGWidth: 200, SVGHeight: 120 });\n    },\n    circle_3: (targetEl) => {\n        const template = (w, h) => [\n            `M78.653 89.204c-14.815 0-29.403-1.096-43.354-4.698-5.227-1.346-10.407-3.069-14.997-5.199-22.996-10.649-27.04-28.502-9.135-43.035 12.18` +\n                `-9.866 26.813-18.04 43.355-24.242C88.515-.718 124.19-3.725 161.228 4.889c13.224 3.07 24.449 8.268 31.902 16.662 8.862 9.992 9.453 20.422 0 30.068-5.817 5.889-13.2` +\n                `24 11.37-21.359 15.786-27.176 14.752-58.579 21.518-93.072 21.8h-.046Zm3.5-4.228c4.408-.282 11.725-.47 18.86-1.253 30.357-3.351 57.579-11.432 79.211-26.842 5.362-3` +\n                `.82 10.134-8.832 12.27-13.875 2.545-5.982 5.817-13.311-6.226-17.352-.454-.156-.727-.563-1.045-.845-10.771-9.146-25.086-14.157-41.719-15.348-39.674-2.85-76.62 3.19` +\n                `5-109.66 18.762-8.18 3.883-15.497 9.177-21.359 14.752-9.725 9.27-8.044 19.889 3.727 28.032 4.862 3.383 10.997 6.233 17.269 8.237 14.406 4.605 30.04 5.544 48.58 5.` +\n                `763l.092-.03ZM130.37 3.573c-24.813-1.88-48.263 1.378-70.44 9.146 22.814-5.481 46.172-9.02 70.44-9.146Z`,\n        ];\n        return drawPath(targetEl, { mode: \"fill\", template, SVGWidth: 200, SVGHeight: 90 });\n    },\n    over_underline: (targetEl) => {\n        const template = (w, h) => [`M 0,0 h ${w}`, `M 0,${h} h ${w}`];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    scribble_1: (targetEl) => {\n        const template = (w, h) => [\n            `M ${w / 2},${h * 0.9} c ${w / 16},0 ${w},1 ${w / 5},1 c 2,0 -${w / 10},-2 -${\n                w / 2\n            },-1` +\n                `c -${w / 20},0 -${w / 5},2 -${w / 5},4 c -2,0 ${w / 10},-1 ${w / 2},${h / 16}` +\n                `c ${w / 25},0 ${w / 10},0 ${w / 5},1 c 0,0 -${w / 10},1 -${w / 8},1` +\n                `c -${w / 40},0 -${w / 16},0 -${w / 4},${h / 22}`,\n        ];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    scribble_2: (targetEl) => {\n        const template = (w, h) => [\n            `M200 3.985c-.228-.332-3.773.541-.01-.006-.811-.037-6.705-1.442-9.978-1.706-1.473.194-2.907.534-4.351.818-1.398.27-2.937.985-4.144.756-` +\n                `9.56-1.782-19.3-1.089-28.955-1.31C118.932 1.767 85.301.942 51.671.45c-13.732-.201-27.492.333-41.233.665C6.561 1.212 3.026 2.363.84 4.838.09 5.684-.262 7.126.223 7` +\n                `.993c.313.554 2.518.79 3.839.728 2.47-.118 4.922-.548 8.096-.936-.96 1.227-1.568 1.865-1.986 2.558-1.368 2.302.029 4 3.203 4.083 24.716.666 49.424 1.4 74.15 2.01 ` +\n                `21.087.52 42.145.34 63.146-1.414 4.495-.374 8.999-.644 14.425-1.026-3.117-1.629-4.723-3.521-8.39-3.535-17.999-.077-36.016-.07-54.005-.534-22.246-.576-44.464-1.58-` +\n                `66.7-2.406-.276-.007-.551-.097-.817-.471 1.016 0 2.033-.021 3.04 0 21.961.506 43.913.998 65.864 1.539 25.249.624 50.47.367 75.642-1.144 5.892-.354 11.765-.93 17.6` +\n                `19-1.54.788-.082 1.416-.99 2.651-1.92Z`,\n        ];\n        return drawPath(targetEl, {\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 17,\n            position: \"bottom\",\n        });\n    },\n    scribble_3: (targetEl) => {\n        const template = (w, h) => [\n            `M133.953 15.961c7.87.502 15.751.975 23.611 1.522 2.027.141 4.055.44 5.999.79 4.118.727 7.202 4.977 2.53 6.707.606.293 1.181.564 1.902.` +\n                `908-8.477 2.069-17.267 2.65-26.203 2.818-19.023.361-38.056.603-57.068 1.088-13.807.355-27.572 1.06-41.369 1.545-3.23.113-6.532.096-9.73-.147-1.548-.118-3.492-.721` +\n                `-4.234-1.42-.93-.88-1.484-2.199-.93-3.1.397-.655 2.812-1.263 4.41-1.33 6.397-.277 12.825-.333 19.243-.474 26.976-.592 53.942-1.156 80.919-1.804 3.742-.09 7.452-.5` +\n                `92 11.173-.908 0-.174-.01-.35-.021-.524-2.717-.197-5.435-.53-8.163-.575-21.865-.383-43.741-1.009-65.607-.936-11.34.04-22.65 1.432-34 2.047-6.898.377-13.88.732-20.` +\n                `779.569-7.044-.17-9.406-3.568-5.34-6.742 3.428-2.677 7.567-4.391 13.984-4.757 16.441-.93 32.798-2.26 49.219-3.27 14.162-.868 28.366-1.516 42.549-2.266.586-.034 1.` +\n                `15-.147 1.641-.45-5.006 0-10.023-.012-15.029.01-1.077 0-2.154.186-3.24.192-18.793.18-37.596.355-56.389.507-10.672.085-21.343.13-32.014.153a65.89 65.89 0 0 1-6.167` +\n                `-.277C1.787 5.555-.02 4.247 0 2.59 0 1.384.89.72 3.293.742c5.874.056 11.748.124 17.622.09C41.045.708 61.186.409 81.317.42c28.408.012 56.827.158 85.225.417 8.686.0` +\n                `8 17.35.7 26.015 1.122 3.23.158 5.832.902 7.024 2.678 1.055 1.572.125 2.21-2.875 1.95a30.51 30.51 0 0 0-2.268-.107c-.397 0-.805.073-1.557.146.721.451 1.306.767 1.` +\n                `777 1.128 2.926 2.238 1.641 4.013-3.272 4.369-13.483.958-26.966 1.91-40.459 2.767-3.334.214-6.752 0-10.118.085-2.31.062-4.609.299-6.909.462l.042.519.011.005Z`,\n        ];\n        return drawPath(targetEl, {\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 32,\n            position: \"bottom\",\n        });\n    },\n    scribble_4: (targetEl) => {\n        const template = (w, h) => [\n            `M96.414 17.157c1.34-2.173 2.462-4.075 3.649-5.944 2.117-3.335 5.528-4.302 9.372-2.694 3.962 1.651 4.89 3.575 3.908 8.073-.205.967-.388` +\n                ` 1.934-.022 3.118 1.513-3.075 3.013-6.15 4.557-9.203 1.306-2.586 4.297-3.433 7.859-2.195 2.765.968 4.395 2.706 3.564 5.922-.529 2.054-1.005 4.118-.918 6.487.463-.` +\n                `859 1.015-1.685 1.371-2.586 1.447-3.673 3.002-7.324 4.2-11.083.896-2.792 2.192-3.955 5.323-3.564 4.772.598 7.049 3.412 5.84 7.986-.626 2.38-1.22 4.77-1.144 7.486.` +\n                `745-1.358 1.544-2.683 2.213-4.074a138.72 138.72 0 0 0 2.926-6.487c2.376-5.66 3.12-4.704 8.724-3.618 3.552.685 5.063 4.031 4.34 7.997-.616 3.423-1.166 6.856-1.749 ` +\n                `10.29l.95.358c.993-2.151 2.062-4.27 2.958-6.454.594-1.456.886-3.042 1.403-4.53 2.43-6.911 2.43-6.813 9.566-5.542.928.163 2.656-.967 3.078-1.923.992-2.26 2.332-2.7` +\n                `16 4.523-2.097 4.297 1.206 8.659 2.184 12.945 3.444 2.796.826 4.319 2.988 4.135 5.889-.173 2.684-.961 5.324-1.274 8.008-.734 6.4-1.361 12.799-2.019 19.21-.065.673` +\n                `.043 1.38-.097 2.031-.551 2.477-.41 5.465-3.476 6.421-2.311.717-6.489-2.194-7.644-5.03-.206-.5-.357-1.01-.918-2.63-1.22 3.27-2.073 5.629-2.991 7.965-2.095 5.345-3` +\n                `.66 5.954-8.874 3.705-.853-.37-2.354-.783-2.786-.359-3.163 3.075-5.971 1.217-8.853-.358-.378-.207-.81-.316-1.188-.457-5.851 7.65-12.502 4.596-15.061-3.944-1.543 3` +\n                `.042-2.883 5.726-4.265 8.399-3.357 6.53-7.783 6.975-12.47 1.25-.485-.587-.992-1.152-1.511-1.75-5.647 6.715-12.848 2.293-15.19-6.063-1.253 2.25-2.257 3.88-3.099 5.` +\n                `596-1.285 2.64-2.883 4.65-6.23 3.868-3.498-.826-6.532-4.085-6.65-7.225-.054-1.424 0-2.847-.475-4.433-1.393 2.879-2.71 5.802-4.19 8.637-3.228 6.204-6.067 6.824-11.` +\n                `67 2.912-.962-.673-2.57-.988-3.704-.728-3.681.837-6.272-.619-8.626-3.248-.691-.783-2.084-1.771-2.807-1.543-4.243 1.347-6.91-.641-9.166-3.836-.378-.543-.8-1.053-1.` +\n                `555-2.031-1.08 2.194-2.008 4.041-2.915 5.9-2.397 4.943-5.528 5.932-10.02 2.835-2.008-1.38-3.713-2.118-6.37-1.738-5.117.728-8.54-3.444-7.762-8.649.227-1.521.378-3.` +\n                `064-.086-4.9-.853 1.369-1.793 2.684-2.548 4.107-2.775 5.259-5.301 5.856-10.074 2.206-.971-.75-1.803-1.674-2.86-2.673-.67.271-1.598 1.043-2.257.858-2.71-.771-5.625` +\n                `-1.423-7.838-3.01-.842-.608-.378-3.683.108-5.465 2.008-7.41 4.232-14.755 6.413-22.11.572-1.945 1.166-3.901 1.943-5.77 1.89-4.52 5.02-5.454 9.145-2.89 1.144.706 2.` +\n                `408 1.217 3.552 1.923 2.364 1.456 4.696 2.988 7.439 4.737C32.423 7.14 37.444 6.64 42.82 10.41c2.602-2.107 1.803-7.17 6.748-6.323 3.369.587 6.478 1.217 7.439 4.878` +\n                ` 2.289-2.281 4.221-5.693 6.877-6.42 2.624-.718 5.992 1.26 9.599 2.216-.044.054.636-.565.96-1.348 1.048-2.499 2.883-3.4 5.42-2.825 2.775.62 5.474 1.304 6.284 4.76.` +\n                `216.89 1.285 2.042 2.159 2.248 7.58 1.793 7.6 1.739 8.108 9.55v.012Z`,\n        ];\n        return drawPath(targetEl, { mode: \"fill\", template, SVGWidth: 200, SVGHeight: 61 });\n    },\n    jagged: (targetEl) => {\n        const template = (w, h) => [\n            `q ${(4 * w) / 3} -${(2 * w) / 3} ${(2 * w) / 3} 0` +\n                `c -${w / 3} ${w / 3} -${w / 3} ${w / 3} ${w / 3} 0`,\n        ];\n        return drawPath(targetEl, { mode: \"pattern\", template });\n    },\n    cross: (targetEl) => {\n        const template = (w, h) => [`M 0,0 L ${w},${h}`, `M 0,${h} L ${w},0`];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    diagonal: (targetEl) => {\n        const template = (w, h) => [`M 0,${h} L${w},0`];\n        return drawPath(targetEl, { mode: \"free\", template });\n    },\n    strikethrough: (targetEl) => drawPath(targetEl, { mode: \"line\", position: \"center\" }),\n    bold: (targetEl) => {\n        const template = (w, h) => [\n            `M136.604 41.568c5.373.513 10.746 1.047 16.12 1.479 14.437 1.13 29.327 4.047 42.858-4.294 4.92-3.04 2.346-13.56-2.687-13.395-.825.02-1.` +\n                `635.062-2.46.082.858-3.677-.34-8.3-3.545-9.41 2.655.062 5.309.104 7.963.165 6.863.185 6.863-14.176 0-14.36A1958.994 1958.994 0 0 0 5.263 5.778C-.4 6.169-2.392 18.` +\n                `455 3.84 19.893c9.727 2.24 19.454 4.335 29.214 6.307-1.085 1.09-1.764 2.671-2.023 4.356-.615.061-1.214.102-1.83.164-6.748.74-6.959 14.587 0 14.361l107.42-3.513h-.` +\n                `016Z`,\n        ];\n        return drawPath(targetEl, { mode: \"fill\", template, SVGWidth: 200, SVGHeight: 46 });\n    },\n    bold_1: (targetEl) => {\n        const template = (w, h) => [\n            `M190.276 34.01c5.618-.25 7.136-6.526 4.444-9.755.037-.25.055-.5.072-.749 7.046-.949 7.01-11.752-.523-11.553-.796.017-1.59.017-2.403.05` +\n                `C196.78 9.573 195.931.8 189.264.983L13.784 5.678c-7.226.2-7.497 9.422-1.499 11.32-2.186 0-4.354 0-6.54-.017-7.696-.05-7.624 11.286 0 11.635 8.22.383 16.423.733 24` +\n                `.643 1.016l-7.823.35c-7.624.349-7.678 11.985 0 11.635 55.915-2.53 111.813-5.077 167.729-7.607h-.018Z`,\n        ];\n        return drawPath(targetEl, { mode: \"fill\", template, SVGWidth: 200, SVGHeight: 42 });\n    },\n    bold_2: (targetEl) => {\n        const template = (w, h) => [\n            `M193.221 20.193c.555 1.245.863 2.005 1.22 2.734 1.399 2.84 2.758 5.757 1.607 9.509-1.21 3.95-3.651 4.208-6.072 4.314-5.059.212-10.129.` +\n                `152-15.178.592-15.873 1.367-31.737 3.585-47.619 4.238-19.921.82-39.862.638-59.802.486-13.938-.106-27.887-.88-41.825-1.428-4.018-.151-8.046-.47-12.064-.896-2.758-.` +\n                `304-4.772-2.46-6.21-6.182-.645-1.656-1.756-2.993-2.798-4.177-2.768-3.13-5.06-6.38-3.899-12.502C.9 15.226.393 13.16.165 11.307c-.715-5.818.903-9.524 4.722-9.646 10` +\n                `.218-.35 20.437-.38 30.655-.577C51.236.78 66.94-.04 82.635.264c14.652.273 29.296 1.655 43.948 2.643 19.822 1.336 39.643 2.02 59.455-.426.923-.121 1.835-.5 2.758-.` +\n                `622 1.329-.183 2.688-.456 4.008-.274 3.829.501 7.073 5.666 7.192 11.21.09 4.466-1.418 6.213-6.775 7.428v-.03Z`,\n        ];\n        return drawPath(targetEl, { mode: \"fill\", template, SVGWidth: 200, SVGHeight: 43 });\n    },\n};\n// Returns the width of the DOMRect object.\nexport const getDOMRectWidth = (el) => el.getBoundingClientRect().width;\n\n/**\n * Draws one or many SVG paths using templates of path shape commands.\n *\n * @param {HTMLElement} textEl\n * @param {String} options.mode Specifies how to draw the path:\n * - \"pattern\": repeat the template along the horizontal axis.\n * - \"line\": draw a simple line (we specify the width & position).\n * - \"free\": draw the path shape using the template only.\n * - \"fill\": used for irregular shapes that do not follow the \"stroke\" design.\n * @param {Function} options.template Returns a list of SVG path\n * commands adapted to the container's size.\n * @returns {String[]}\n */\nfunction drawPath(textEl, options) {\n    // Note: cannot use getBoundingClientRect as we want to be able to draw\n    // text highlights in snippets/add page dialogs where iframe is scaled.\n    const width = textEl.offsetWidth;\n    const height = textEl.offsetHeight;\n    options = { ...options, width, height };\n    const yStart = options.position === \"center\" ? height / 2 : height;\n\n    switch (options.mode) {\n        case \"pattern\": {\n            let i = 0;\n            const d = [];\n            const nbrChars = textEl.textContent.length;\n            const w = width / nbrChars,\n                h = height * 0.2;\n            while (i < nbrChars) {\n                d.push(options.template(w, h));\n                i++;\n            }\n            return buildPath([`M 0,${yStart} ${d.join(\" \")}`], options);\n        }\n        case \"line\": {\n            return buildPath([`M 0,${yStart} h ${width}`], options);\n        }\n    }\n    return buildPath(options.template(width, height), options);\n}\n\n/**\n * Used to build the SVG <path/>, it should mainly adapt it to take into\n * consideration some cases where the shape is a \"filled path\" instead\n * of a single line stroke.\n *\n * @param {String[]} templates\n * @param {Object} options\n * @returns {Element[]}\n */\nfunction buildPath(templates, options) {\n    return templates.map((d) => {\n        const path = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n        path.setAttribute(\"stroke-width\", \"var(--text-highlight-width)\");\n        path.setAttribute(\"stroke\", \"var(--text-highlight-color)\");\n        path.setAttribute(\"stroke-linecap\", \"round\");\n        if (options.mode === \"fill\") {\n            const wScale = options.width / options.SVGWidth;\n            let hScale = options.height / options.SVGHeight;\n            const transforms = [];\n            if (options.position === \"bottom\") {\n                hScale *= 0.3;\n                transforms.push(`translate(0 ${options.height * 0.8})`);\n            }\n            transforms.push(`scale(${wScale}, ${hScale})`);\n            path.setAttribute(\"fill\", \"var(--text-highlight-color)\");\n            path.setAttribute(\"transform\", transforms.join(\" \"));\n        }\n        path.setAttribute(\"d\", d);\n        return path;\n    });\n}\n\n/**\n * Returns a new highlight SVG adapted to the text container.\n *\n * @param {HTMLElement} textEl\n * @param {String} highlightID\n */\nexport function drawTextHighlightSVG(textEl, highlightID) {\n    const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n    svg.setAttribute(\"fill\", \"none\");\n    svg.classList.add(\n        \"o_text_highlight_svg\",\n        // Identifies DOM content that should not be merged by the editor, even\n        // on identical parents.\n        \"o_content_no_merge\",\n        \"position-absolute\",\n        \"overflow-visible\",\n        \"top-0\",\n        \"start-0\",\n        \"w-100\",\n        \"h-100\",\n        \"pe-none\"\n    );\n    _textHighlightFactory[highlightID](textEl).forEach((pathEl) => {\n        pathEl.classList.add(`o_text_highlight_path_${highlightID}`);\n        svg.appendChild(pathEl);\n    });\n    return svg;\n}\n\n/**\n * Divides the content of a text container into multiple\n * `.o_text_highlight_item` units, and applies the highlight\n * on each unit.\n *\n * @param {HTMLElement} topTextEl\n * @param {String} highlightID\n */\nexport function applyTextHighlight(topTextEl, highlightID) {\n    const endHighlightUpdate = () =>\n        topTextEl.dispatchEvent(new Event(\"text_highlight_added\", { bubbles: true }));\n    // Don't reapply the effects to a highlighted text.\n    // If the target is invisible, we still need to notify the public widget\n    // that a highlight was detected (It's needed anyway, so the public widget\n    // can link the element to its observer, which tracks size changes and\n    // adapts the highlights accordingly).\n    if (topTextEl.querySelector(\".o_text_highlight_item\") || !isVisible(topTextEl)) {\n        return endHighlightUpdate();\n    }\n    const style = window.getComputedStyle(topTextEl);\n    if (!style.getPropertyValue(\"--text-highlight-width\")) {\n        // The default value for `--text-highlight-width` is 0.1em.\n        topTextEl.style.setProperty(\n            \"--text-highlight-width\",\n            `${Math.round(parseFloat(style.fontSize) * 0.1)}px`\n        );\n    }\n    const lines = [];\n    let lineIndex = 0;\n    const nodeIsBR = (node) => node.nodeName === \"BR\";\n    const isRTL = (el) => window.getComputedStyle(el).direction === \"rtl\";\n\n    [...topTextEl.childNodes].forEach((child) => {\n        // We consider `<br/>` tags as full text lines to ease\n        // excluding them when the highlight is applied on the DOM.\n        if (nodeIsBR(child)) {\n            lines[++lineIndex] = [child];\n            return lineIndex++;\n        }\n        const textLines = splitNodeLines(child);\n\n        // Special case: The text lines detection code in `splitNodeLines()`\n        // (based on `getClientRects()`) can't handle a situation when a line\n        // exactly ends with the current child node. We need to handle this\n        // manually by checking if the current child node is the last one in\n        // the line (taking into account the RTL direction).\n        // TODO: Improve this.\n        let lastNodeInLine = false;\n        if (child.textContent && child.nextSibling?.textContent) {\n            const range = document.createRange();\n            const lastCurrentText = selectAllTextNodes(child).at(-1);\n            range.setStart(lastCurrentText, lastCurrentText.length - 1);\n            range.setEnd(lastCurrentText, lastCurrentText.length);\n            // Get the \"END\" position of the last text node in current child.\n            const currentEnd = range.getBoundingClientRect()[isRTL(topTextEl) ? \"left\" : \"right\"];\n            const firstnextText = selectAllTextNodes(child.nextSibling)[0];\n            range.setStart(firstnextText, 0);\n            range.setEnd(firstnextText, 1);\n            // Get the \"START\" position of the first text node in the next\n            // sibling.\n            const nextStart = range.getBoundingClientRect()[isRTL(topTextEl) ? \"right\" : \"left\"];\n            // The next sibling starts before the end of the current node\n            // => Line break detected.\n            lastNodeInLine = nextStart + 1 < currentEnd;\n        }\n\n        // for each text line detected, we add the content as new\n        // line and adjust the line index accordingly.\n        textLines.map((node, i, { length }) => {\n            if (!lines[lineIndex]) {\n                lines[lineIndex] = [];\n            }\n            lines[lineIndex].push(node);\n            if (i !== length - 1 || lastNodeInLine) {\n                lineIndex++;\n            }\n        });\n    });\n    topTextEl.replaceChildren(\n        ...lines.map((textLine) =>\n            // First we add text content to be able to build svg paths\n            // correctly (`<br/>` tags are excluded).\n            nodeIsBR(textLine[0]) ? textLine[0] : createHighlightContainer(textLine)\n        )\n    );\n    // Build and set highlight SVGs.\n    [...topTextEl.querySelectorAll(\".o_text_highlight_item\")].forEach((container) => {\n        container.append(\n            drawTextHighlightSVG(container, highlightID || getCurrentTextHighlight(topTextEl))\n        );\n    });\n    endHighlightUpdate();\n}\n\n/**\n * Used to rollback the @see applyTextHighlight behaviour.\n *\n * @param {HTMLElement} topTextEl\n */\nexport function removeTextHighlight(topTextEl) {\n    topTextEl.dispatchEvent(new Event(\"text_highlight_remove\", { bubbles: true }));\n    // Simply replace every `<span class=\"o_text_highlight_item\">\n    // textNode1 [textNode2,...]<svg .../></span>` by `textNode1\n    // [textNode2,...]`.\n    [...topTextEl.querySelectorAll(\".o_text_highlight_item\")].forEach((unit) => {\n        unit.after(...[...unit.childNodes].filter((node) => node.tagName !== \"svg\"));\n        unit.remove();\n    });\n    // Prevents incorrect text lines detection on the next updates.\n    let child = topTextEl.firstElementChild;\n    while (child) {\n        const next = child.nextElementSibling;\n        // Merge identical elements.\n        if (next && next === child.nextSibling && child.cloneNode().isEqualNode(next.cloneNode())) {\n            child.replaceChildren(...child.childNodes, ...next.childNodes);\n            next.remove();\n        } else {\n            child = next;\n        }\n    }\n    topTextEl.normalize();\n}\n\n/**\n * Used to wrap text nodes in a single \"text highlight\" unit.\n *\n * @param {Node[]} nodes\n * @returns {HTMLElement} The one line text element that should contain\n * the highlight SVG.\n */\nfunction createHighlightContainer(nodes) {\n    const highlightContainer = document.createElement(\"span\");\n    highlightContainer.className = \"o_text_highlight_item\";\n    highlightContainer.append(...nodes);\n    return highlightContainer;\n}\n\n/**\n * Used to get the current text highlight id from the top `.o_text_highlight`\n * container class.\n *\n * @param {HTMLElement} el\n * @returns {String}\n */\nexport function getCurrentTextHighlight(el) {\n    const topTextEl = el.closest(\".o_text_highlight\");\n    const match = topTextEl?.className.match(/o_text_highlight_(?<value>[\\w]+)/);\n    let highlight = \"\";\n    if (match) {\n        highlight = match.groups.value;\n    }\n    return highlight;\n}\n\n/**\n * Returns a list of detected lines in the content of a text node.\n *\n * @param {Node} node\n */\nfunction splitNodeLines(node) {\n    const isTextContainer =\n        node.childNodes.length === 1 && node.firstChild.nodeType === Node.TEXT_NODE;\n    if (node.nodeType !== Node.TEXT_NODE && !isTextContainer) {\n        return [node];\n    }\n    const text = node.textContent;\n    const textNode = isTextContainer ? node.firstChild : node;\n    const lines = [];\n    const range = document.createRange();\n    let i = -1;\n    while (++i < text.length) {\n        range.setStart(textNode, 0);\n        range.setEnd(textNode, i + 1);\n        const clientRects = range.getClientRects().length || 1;\n        const lineIndex = clientRects - 1;\n        const currentText = lines[lineIndex];\n        lines[lineIndex] = (currentText || \"\") + text.charAt(i);\n    }\n    // Return the original node when no lines were detected.\n    if (lines.length === 1) {\n        return [node];\n    }\n    return lines.map((line) => {\n        if (isTextContainer) {\n            const wrapper = node.cloneNode();\n            wrapper.appendChild(document.createTextNode(line));\n            return wrapper;\n        }\n        return document.createTextNode(line);\n    });\n}\n\n/**\n * Get all text nodes inside a parent DOM element.\n *\n * @param {Node} topNode\n * @returns {Node[]} List of text \"childNodes\" or the element itself\n * (if it's a text node).\n */\nexport function selectAllTextNodes(topNode) {\n    const textNodes = [];\n    const selectTextNodes = (node) => {\n        if (node.nodeType === Node.TEXT_NODE) {\n            textNodes.push(node);\n        } else {\n            [...node.childNodes].forEach((child) => selectTextNodes(child));\n        }\n    };\n    selectTextNodes(topNode);\n    return textNodes;\n}\n\n/**\n * Used to get the node of a text element in which a selection starts/ends.\n *\n * @param {HTMLElement} textEl The parent text element.\n * @param {Number} offset The selection offset in parent element.\n * @returns {[Node, Number]} The node found in the cursor position\n * and the new offset compared to that node.\n */\nexport function getOffsetNode(textEl, offset) {\n    let index = 0,\n        offsetNode;\n    for (const node of selectAllTextNodes(textEl)) {\n        const stepLength = node.textContent.length;\n        if (index + stepLength < offset - 1) {\n            index += stepLength;\n        } else {\n            offsetNode = node;\n            break;\n        }\n    }\n    return [offsetNode, offset - index];\n}\n", "import { descendants } from \"@html_editor/utils/dom_traversal\";\nimport { memoize } from \"@web/core/utils/functions\";\n\nexport const textHighlightFactory = {\n    underline: (params) => drawPath({ ...params, mode: \"line\" }),\n    freehand_1: (params) => {\n        const template = (w, h) => [\n            `M 0,${h * 1.1} C ${w / 8},${h * 1.05} ${w / 4},${h} ${w},${h}`,\n        ];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    freehand_2: (params) => {\n        const template = (w, h) => [\n            `M181.27 13.873c-.451-1.976-.993-3.421-1.072-4.9-.125-2.214-.61-4.856.384-6.539.756-1.287 3.636-2.055 5.443-1.852 3.455.395 7.001 1.231` +\n                ` 10.14 2.676 1.728.802 3.174 3.06 3.817 4.98.237.712-1.953 2.824-3.399 3.4-2.766 1.095-5.748 1.75-8.706 2.179-2.394.339-4.879.068-6.584.068l-.023-.012ZM8.416 3.90` +\n                `2c3.862.26 7.78.249 11.574.926 1.65.294 3.027 2.033 4.54 3.117-1.095 1.186-1.987 2.982-3.343 3.456a67.118 67.118 0 0 1-11.19 2.823c-3.253.53-6.494-.339-8.617-2.98` +\n                `1C.364 9.978-.302 7.686.138 6.263c.361-1.152 2.54-2 4.077-2.44 1.287-.372 2.789.046 4.2.102v-.023Zm154.267 9.983c-4.291-.305-8.153-1.58-9.915-5.623-.745-1.694-.39` +\n                `5-4.382.474-6.121 1.073-2.168 3.512-1.965 5.613-1.005 2.541 1.174 5.251 2.157 7.509 3.76 1.502 1.073 3.557 3.445 3.207 4.574-.519 1.694-2.857 2.913-4.562 4.133-.5` +\n                `76.406-1.592.203-2.326.282ZM72.58 17.42c-2.733-1.807-5.307-3.004-7.137-4.913-.892-.925-.892-3.376-.361-4.776.407-1.05 2.304-2.112 3.546-2.135 3.602-.056 7.238.215` +\n                ` 10.818.723 3.828.542 5.15 4.1 2.213 6.539-2.439 2.021-5.77 2.958-9.079 4.562Zm30.795-.802c-2.507-1.536-5.228-2.823-7.397-4.743-.925-.813-1.377-3.297-.813-4.359.6` +\n                `78-1.265 2.677-2.507 4.11-2.518 3.016-.023 6.155.418 9.001 1.389 1.412.485 3.173 2.552 3.185 3.907 0 1.57-1.423 3.557-2.801 4.619-1.152.892-3.139.711-4.743 1.005-` +\n                `.181.226-.35.463-.531.689l-.011.01Zm-59.704-1.457c-2.066-1.163-4.788-2.224-6.82-4.054-.915-.824-1.04-3.478-.407-4.765.486-.983 2.722-1.559 4.156-1.502 2.676.101 5` +\n                `.398.542 7.95 1.332 1.457.452 3.523 1.75 3.681 2.891.18 1.31-1.13 3.309-2.383 4.201-1.411 1.005-3.466 1.118-6.188 1.886l.011.011Zm88.489-1.863c-2.643-1.48-5.567-2` +\n                `.62-7.803-4.574-1.005-.88-1.31-3.692-.667-5.002.509-1.04 2.982-1.615 4.529-1.513 2.032.135 4.054 1.027 6.007 1.772 2.485.95 5.026 2.236 4.382 5.455-.644 3.15-3.49` +\n                ` 2.947-5.963 3.004-.169.293-.327.575-.496.87l.011-.012Z`,\n        ];\n        return drawPath({\n            ...params,\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 18,\n            position: \"bottom\",\n        });\n    },\n    freehand_3: (params) => {\n        const template = (w, h) => [\n            `M189.705 18.285c-3.99.994-7.968 2.015-11.958 2.972-1.415.344-2.926 1.008-4.278.727-6.305-1.327-12.568-3.036-18.874-4.376-1.995-.42-4.2` +\n                `46-.701-6.133-.038-5.867 2.067-11.54 2.386-17.374-.242-1.491-.676-3.56-.421-5.125.217-5.523 2.22-10.789 3.597-16.494.127-1.64-.995-4.675-.038-6.584 1.148-6.102 3.` +\n                `789-12.01 4.414-18.198.434-.998-.638-2.681-.638-3.754-.115-6.852 3.355-13.404 2.858-20.043-1.008-1.5-.867-4.02-.6-5.608.307-7.528 4.35-14.842 5.702-22.07-.638-2.1` +\n                `44-1.875-3.71-.37-5.394 1.046-4.622 3.89-9.565 6.327-15.367 4.286C6.338 20.989.505 13.067.022 5.949-.085 4.38.194 1.753.955 1.332 2.253.617 4.537.553 5.588 1.51 7` +\n                `.55 3.27 9.18 5.77 10.52 8.296c2.82 5.269 4.15 5.766 8.504 2.156 1.555-1.288 2.992-2.768 4.396-4.286 4.022-4.311 7.143-4.465 11.26-.472 7.068 6.837 8.226 7.067 15` +\n                `.979 1.314 3.721-2.755 7.206-2.653 10.627.128 4.987 4.056 9.791 4.49 14.853.191 2.702-2.296 5.78-2.296 8.45.115 4.29 3.89 8.45 3.33 12.719.166.847-.638 1.705-1.26` +\n                `3 2.552-1.914 3.035-2.309 6.048-2.5 9.019.166 3.453 3.087 7.12 3.15 10.616.472 4.107-3.138 7.85-3.342 12.16-.306 3.668 2.59 7.83 1.964 11.594-.255 3.935-2.322 7.6` +\n                `67-2.488 11.409.408.365.28.794.612 1.213.65 6.799.549 13.522 3.394 20.428.779 1.887-.715 3.914-1.034 5.899-1.148 3.313-.192 6.659-.358 9.941 0 1.993.23 4.354.905 ` +\n                `5.737 2.436 1.308 1.429 2.113 4.235 2.123 6.442.022 3.023-2.424 3.431-4.472 3.597-1.887.153-3.796.038-5.695.038-.053-.216-.106-.446-.16-.663l.032-.025Z`,\n        ];\n        return drawPath({\n            ...params,\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 24,\n            position: \"bottom\",\n        });\n    },\n    double: (params) => {\n        const template = (w, h) => [`M 0,${h * 0.9} h ${w}`, `M 0,${h * 1.1} h ${w}`];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    wavy: (params) => {\n        const template = (w, h) => [\n            `c ${w / 4},0 ${w / 4},-${h / 2} ${w / 2},-${h / 2}` +\n                `c ${w / 4},0 ${w / 4},${h / 2} ${w / 2},${h / 2}`,\n        ];\n        return drawPath({ ...params, mode: \"pattern\", template });\n    },\n    circle_1: (params) => {\n        const template = (w, h) => [\n            `M ${w / 2.88},${h / 1.1} C ${w / 1.1},${h / 1.05} ${w * 1.05},${h / 1.1} ${\n                w * 1.023\n            },${h / 2.32}` +\n                `C ${w}, ${h / 14.6} ${w / 1.411},0 ${w / 2},0 S -2,${h / 14.6} -2,${h / 2.2}` +\n                `S ${w / 4.24},${h} ${w / 1.36},${h * 1.04}`,\n        ];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    circle_2: (params) => {\n        const template = (w, h) => [\n            `M112.58 21.164h18.516c-.478-.176-1.722-.64-2.967-1.105.101-.401.214-.803.315-1.192 12.255 2.912 24.561 5.573 36.716 8.823 5.896 1.582 ` +\n                `11.628 3.967 17.171 6.527 10.433 4.832 14.418 14.22 16.479 24.739.377 1.92.566 3.878.83 5.823 2.212 15.94-5.858 23.986-21.595 33.813-.993.615-2.288.79-3.181 1.494` +\n                `-14.229 11.308-31.412 14.32-48.608 17.107-29.01 4.694-57.431 2.209-84.91-8.372-8.145-3.138-16.164-6.853-23.706-11.22C6.176 90.986 1.16 80.053.193 67.25c-1.798-23.` +\n                `809 9.025-42.485 30.356-53.304C44.678 6.793 59.8 3.367 75.45 2.375 90.583 1.42 105.793.379 120.927.78c16.089.427 32.041 3.05 46.911 9.84 2.074.941 3.67 2.912 4.91` +\n                `5 5.083-9.73-1.443-19.433-2.987-29.175-4.305-4.89-.665-9.842-1.067-14.77-1.33-23.82-1.28-47.376.514-70.391 7.003a133.771 133.771 0 0 0-22.639 8.648c-17.9 8.786-27` +\n                `.616 26.935-25.567 46.364.666 6.263 3.507 11.133 9.05 14.308 26.862 15.401 55.748 21.965 86.645 19.819 15.561-1.08 31.01-2.787 45.767-8.284 11.099-4.142 21.658-9.` +\n                `25 30.595-17.195 9.779-8.698 11.715-18.55 5.669-30.249-1.131-2.196-3.256-4.079-5.33-5.56-7.981-5.736-17.773-7.48-26.459-11.534-13.249-6.175-27.541-6.916-41.343-10` +\n                `.167-.817-.188-1.571-.64-2.35-.966.037-.364.088-.728.125-1.092Z`,\n        ];\n        return drawPath({ ...params, mode: \"fill\", template, SVGWidth: 200, SVGHeight: 120 });\n    },\n    circle_3: (params) => {\n        const template = (w, h) => [\n            `M78.653 89.204c-14.815 0-29.403-1.096-43.354-4.698-5.227-1.346-10.407-3.069-14.997-5.199-22.996-10.649-27.04-28.502-9.135-43.035 12.18` +\n                `-9.866 26.813-18.04 43.355-24.242C88.515-.718 124.19-3.725 161.228 4.889c13.224 3.07 24.449 8.268 31.902 16.662 8.862 9.992 9.453 20.422 0 30.068-5.817 5.889-13.2` +\n                `24 11.37-21.359 15.786-27.176 14.752-58.579 21.518-93.072 21.8h-.046Zm3.5-4.228c4.408-.282 11.725-.47 18.86-1.253 30.357-3.351 57.579-11.432 79.211-26.842 5.362-3` +\n                `.82 10.134-8.832 12.27-13.875 2.545-5.982 5.817-13.311-6.226-17.352-.454-.156-.727-.563-1.045-.845-10.771-9.146-25.086-14.157-41.719-15.348-39.674-2.85-76.62 3.19` +\n                `5-109.66 18.762-8.18 3.883-15.497 9.177-21.359 14.752-9.725 9.27-8.044 19.889 3.727 28.032 4.862 3.383 10.997 6.233 17.269 8.237 14.406 4.605 30.04 5.544 48.58 5.` +\n                `763l.092-.03ZM130.37 3.573c-24.813-1.88-48.263 1.378-70.44 9.146 22.814-5.481 46.172-9.02 70.44-9.146Z`,\n        ];\n        return drawPath({ ...params, mode: \"fill\", template, SVGWidth: 200, SVGHeight: 90 });\n    },\n    over_underline: (params) => {\n        const template = (w, h) => [`M 0,0 h ${w}`, `M 0,${h} h ${w}`];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    scribble_1: (params) => {\n        const template = (w, h) => [\n            `M ${w / 2},${h * 0.9} c ${w / 16},0 ${w},1 ${w / 5},1 c 2,0 -${w / 10},-2 -${\n                w / 2\n            },-1` +\n                `c -${w / 20},0 -${w / 5},2 -${w / 5},4 c -2,0 ${w / 10},-1 ${w / 2},${h / 16}` +\n                `c ${w / 25},0 ${w / 10},0 ${w / 5},1 c 0,0 -${w / 10},1 -${w / 8},1` +\n                `c -${w / 40},0 -${w / 16},0 -${w / 4},${h / 22}`,\n        ];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    scribble_2: (params) => {\n        const template = (w, h) => [\n            `M200 3.985c-.228-.332-3.773.541-.01-.006-.811-.037-6.705-1.442-9.978-1.706-1.473.194-2.907.534-4.351.818-1.398.27-2.937.985-4.144.756-` +\n                `9.56-1.782-19.3-1.089-28.955-1.31C118.932 1.767 85.301.942 51.671.45c-13.732-.201-27.492.333-41.233.665C6.561 1.212 3.026 2.363.84 4.838.09 5.684-.262 7.126.223 7` +\n                `.993c.313.554 2.518.79 3.839.728 2.47-.118 4.922-.548 8.096-.936-.96 1.227-1.568 1.865-1.986 2.558-1.368 2.302.029 4 3.203 4.083 24.716.666 49.424 1.4 74.15 2.01 ` +\n                `21.087.52 42.145.34 63.146-1.414 4.495-.374 8.999-.644 14.425-1.026-3.117-1.629-4.723-3.521-8.39-3.535-17.999-.077-36.016-.07-54.005-.534-22.246-.576-44.464-1.58-` +\n                `66.7-2.406-.276-.007-.551-.097-.817-.471 1.016 0 2.033-.021 3.04 0 21.961.506 43.913.998 65.864 1.539 25.249.624 50.47.367 75.642-1.144 5.892-.354 11.765-.93 17.6` +\n                `19-1.54.788-.082 1.416-.99 2.651-1.92Z`,\n        ];\n        return drawPath({\n            ...params,\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 17,\n            position: \"bottom\",\n        });\n    },\n    scribble_3: (params) => {\n        const template = (w, h) => [\n            `M133.953 15.961c7.87.502 15.751.975 23.611 1.522 2.027.141 4.055.44 5.999.79 4.118.727 7.202 4.977 2.53 6.707.606.293 1.181.564 1.902.` +\n                `908-8.477 2.069-17.267 2.65-26.203 2.818-19.023.361-38.056.603-57.068 1.088-13.807.355-27.572 1.06-41.369 1.545-3.23.113-6.532.096-9.73-.147-1.548-.118-3.492-.721` +\n                `-4.234-1.42-.93-.88-1.484-2.199-.93-3.1.397-.655 2.812-1.263 4.41-1.33 6.397-.277 12.825-.333 19.243-.474 26.976-.592 53.942-1.156 80.919-1.804 3.742-.09 7.452-.5` +\n                `92 11.173-.908 0-.174-.01-.35-.021-.524-2.717-.197-5.435-.53-8.163-.575-21.865-.383-43.741-1.009-65.607-.936-11.34.04-22.65 1.432-34 2.047-6.898.377-13.88.732-20.` +\n                `779.569-7.044-.17-9.406-3.568-5.34-6.742 3.428-2.677 7.567-4.391 13.984-4.757 16.441-.93 32.798-2.26 49.219-3.27 14.162-.868 28.366-1.516 42.549-2.266.586-.034 1.` +\n                `15-.147 1.641-.45-5.006 0-10.023-.012-15.029.01-1.077 0-2.154.186-3.24.192-18.793.18-37.596.355-56.389.507-10.672.085-21.343.13-32.014.153a65.89 65.89 0 0 1-6.167` +\n                `-.277C1.787 5.555-.02 4.247 0 2.59 0 1.384.89.72 3.293.742c5.874.056 11.748.124 17.622.09C41.045.708 61.186.409 81.317.42c28.408.012 56.827.158 85.225.417 8.686.0` +\n                `8 17.35.7 26.015 1.122 3.23.158 5.832.902 7.024 2.678 1.055 1.572.125 2.21-2.875 1.95a30.51 30.51 0 0 0-2.268-.107c-.397 0-.805.073-1.557.146.721.451 1.306.767 1.` +\n                `777 1.128 2.926 2.238 1.641 4.013-3.272 4.369-13.483.958-26.966 1.91-40.459 2.767-3.334.214-6.752 0-10.118.085-2.31.062-4.609.299-6.909.462l.042.519.011.005Z`,\n        ];\n        return drawPath({\n            ...params,\n            mode: \"fill\",\n            template,\n            SVGWidth: 200,\n            SVGHeight: 32,\n            position: \"bottom\",\n        });\n    },\n    scribble_4: (params) => {\n        const template = (w, h) => [\n            `M96.414 17.157c1.34-2.173 2.462-4.075 3.649-5.944 2.117-3.335 5.528-4.302 9.372-2.694 3.962 1.651 4.89 3.575 3.908 8.073-.205.967-.388` +\n                ` 1.934-.022 3.118 1.513-3.075 3.013-6.15 4.557-9.203 1.306-2.586 4.297-3.433 7.859-2.195 2.765.968 4.395 2.706 3.564 5.922-.529 2.054-1.005 4.118-.918 6.487.463-.` +\n                `859 1.015-1.685 1.371-2.586 1.447-3.673 3.002-7.324 4.2-11.083.896-2.792 2.192-3.955 5.323-3.564 4.772.598 7.049 3.412 5.84 7.986-.626 2.38-1.22 4.77-1.144 7.486.` +\n                `745-1.358 1.544-2.683 2.213-4.074a138.72 138.72 0 0 0 2.926-6.487c2.376-5.66 3.12-4.704 8.724-3.618 3.552.685 5.063 4.031 4.34 7.997-.616 3.423-1.166 6.856-1.749 ` +\n                `10.29l.95.358c.993-2.151 2.062-4.27 2.958-6.454.594-1.456.886-3.042 1.403-4.53 2.43-6.911 2.43-6.813 9.566-5.542.928.163 2.656-.967 3.078-1.923.992-2.26 2.332-2.7` +\n                `16 4.523-2.097 4.297 1.206 8.659 2.184 12.945 3.444 2.796.826 4.319 2.988 4.135 5.889-.173 2.684-.961 5.324-1.274 8.008-.734 6.4-1.361 12.799-2.019 19.21-.065.673` +\n                `.043 1.38-.097 2.031-.551 2.477-.41 5.465-3.476 6.421-2.311.717-6.489-2.194-7.644-5.03-.206-.5-.357-1.01-.918-2.63-1.22 3.27-2.073 5.629-2.991 7.965-2.095 5.345-3` +\n                `.66 5.954-8.874 3.705-.853-.37-2.354-.783-2.786-.359-3.163 3.075-5.971 1.217-8.853-.358-.378-.207-.81-.316-1.188-.457-5.851 7.65-12.502 4.596-15.061-3.944-1.543 3` +\n                `.042-2.883 5.726-4.265 8.399-3.357 6.53-7.783 6.975-12.47 1.25-.485-.587-.992-1.152-1.511-1.75-5.647 6.715-12.848 2.293-15.19-6.063-1.253 2.25-2.257 3.88-3.099 5.` +\n                `596-1.285 2.64-2.883 4.65-6.23 3.868-3.498-.826-6.532-4.085-6.65-7.225-.054-1.424 0-2.847-.475-4.433-1.393 2.879-2.71 5.802-4.19 8.637-3.228 6.204-6.067 6.824-11.` +\n                `67 2.912-.962-.673-2.57-.988-3.704-.728-3.681.837-6.272-.619-8.626-3.248-.691-.783-2.084-1.771-2.807-1.543-4.243 1.347-6.91-.641-9.166-3.836-.378-.543-.8-1.053-1.` +\n                `555-2.031-1.08 2.194-2.008 4.041-2.915 5.9-2.397 4.943-5.528 5.932-10.02 2.835-2.008-1.38-3.713-2.118-6.37-1.738-5.117.728-8.54-3.444-7.762-8.649.227-1.521.378-3.` +\n                `064-.086-4.9-.853 1.369-1.793 2.684-2.548 4.107-2.775 5.259-5.301 5.856-10.074 2.206-.971-.75-1.803-1.674-2.86-2.673-.67.271-1.598 1.043-2.257.858-2.71-.771-5.625` +\n                `-1.423-7.838-3.01-.842-.608-.378-3.683.108-5.465 2.008-7.41 4.232-14.755 6.413-22.11.572-1.945 1.166-3.901 1.943-5.77 1.89-4.52 5.02-5.454 9.145-2.89 1.144.706 2.` +\n                `408 1.217 3.552 1.923 2.364 1.456 4.696 2.988 7.439 4.737C32.423 7.14 37.444 6.64 42.82 10.41c2.602-2.107 1.803-7.17 6.748-6.323 3.369.587 6.478 1.217 7.439 4.878` +\n                ` 2.289-2.281 4.221-5.693 6.877-6.42 2.624-.718 5.992 1.26 9.599 2.216-.044.054.636-.565.96-1.348 1.048-2.499 2.883-3.4 5.42-2.825 2.775.62 5.474 1.304 6.284 4.76.` +\n                `216.89 1.285 2.042 2.159 2.248 7.58 1.793 7.6 1.739 8.108 9.55v.012Z`,\n        ];\n        return drawPath({ ...params, mode: \"fill\", template, SVGWidth: 200, SVGHeight: 61 });\n    },\n    jagged: (params) => {\n        const template = (w, h) => [\n            `q ${(4 * w) / 3} -${(2 * w) / 3} ${(2 * w) / 3} 0` +\n                `c -${w / 3} ${w / 3} -${w / 3} ${w / 3} ${w / 3} 0`,\n        ];\n        return drawPath({ ...params, mode: \"pattern\", template });\n    },\n    cross: (params) => {\n        const template = (w, h) => [`M 0,0 L ${w},${h}`, `M 0,${h} L ${w},0`];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    diagonal: (params) => {\n        const template = (w, h) => [`M 0,${h} L${w},0`];\n        return drawPath({ ...params, mode: \"free\", template });\n    },\n    strikethrough: (params) => drawPath({ ...params, mode: \"line\", position: \"center\" }),\n    bold: (params) => {\n        const template = (w, h) => [\n            `M136.604 41.568c5.373.513 10.746 1.047 16.12 1.479 14.437 1.13 29.327 4.047 42.858-4.294 4.92-3.04 2.346-13.56-2.687-13.395-.825.02-1.` +\n                `635.062-2.46.082.858-3.677-.34-8.3-3.545-9.41 2.655.062 5.309.104 7.963.165 6.863.185 6.863-14.176 0-14.36A1958.994 1958.994 0 0 0 5.263 5.778C-.4 6.169-2.392 18.` +\n                `455 3.84 19.893c9.727 2.24 19.454 4.335 29.214 6.307-1.085 1.09-1.764 2.671-2.023 4.356-.615.061-1.214.102-1.83.164-6.748.74-6.959 14.587 0 14.361l107.42-3.513h-.` +\n                `016Z`,\n        ];\n        return drawPath({ ...params, mode: \"fill\", template, SVGWidth: 200, SVGHeight: 46 });\n    },\n    bold_1: (params) => {\n        const template = (w, h) => [\n            `M190.276 34.01c5.618-.25 7.136-6.526 4.444-9.755.037-.25.055-.5.072-.749 7.046-.949 7.01-11.752-.523-11.553-.796.017-1.59.017-2.403.05` +\n                `C196.78 9.573 195.931.8 189.264.983L13.784 5.678c-7.226.2-7.497 9.422-1.499 11.32-2.186 0-4.354 0-6.54-.017-7.696-.05-7.624 11.286 0 11.635 8.22.383 16.423.733 24` +\n                `.643 1.016l-7.823.35c-7.624.349-7.678 11.985 0 11.635 55.915-2.53 111.813-5.077 167.729-7.607h-.018Z`,\n        ];\n        return drawPath({ ...params, mode: \"fill\", template, SVGWidth: 200, SVGHeight: 42 });\n    },\n    bold_2: (params) => {\n        const template = (w, h) => [\n            `M193.221 20.193c.555 1.245.863 2.005 1.22 2.734 1.399 2.84 2.758 5.757 1.607 9.509-1.21 3.95-3.651 4.208-6.072 4.314-5.059.212-10.129.` +\n                `152-15.178.592-15.873 1.367-31.737 3.585-47.619 4.238-19.921.82-39.862.638-59.802.486-13.938-.106-27.887-.88-41.825-1.428-4.018-.151-8.046-.47-12.064-.896-2.758-.` +\n                `304-4.772-2.46-6.21-6.182-.645-1.656-1.756-2.993-2.798-4.177-2.768-3.13-5.06-6.38-3.899-12.502C.9 15.226.393 13.16.165 11.307c-.715-5.818.903-9.524 4.722-9.646 10` +\n                `.218-.35 20.437-.38 30.655-.577C51.236.78 66.94-.04 82.635.264c14.652.273 29.296 1.655 43.948 2.643 19.822 1.336 39.643 2.02 59.455-.426.923-.121 1.835-.5 2.758-.` +\n                `622 1.329-.183 2.688-.456 4.008-.274 3.829.501 7.073 5.666 7.192 11.21.09 4.466-1.418 6.213-6.775 7.428v-.03Z`,\n        ];\n        return drawPath({ ...params, mode: \"fill\", template, SVGWidth: 200, SVGHeight: 43 });\n    },\n};\n\n/**\n * Divides the content of a text container into multiple\n * `.o_text_highlight_item` units, and applies the highlight\n * on each unit.\n *\n * @param {HTMLElement} highlightEl\n * @param {String} highlightID\n */\nexport function makeHighlightSvgs(highlightEl, highlightID) {\n    const style = window.getComputedStyle(highlightEl);\n    if (!style.getPropertyValue(\"--text-highlight-width\")) {\n        // The default value for `--text-highlight-width` is 0.1em.\n        highlightEl.style.setProperty(\n            \"--text-highlight-width\",\n            `${Math.round(parseFloat(style.fontSize) * 0.1)}px`\n        );\n    }\n\n    const textNodes = descendants(highlightEl).filter((el) => el.nodeType === Node.TEXT_NODE);\n    const rects = textNodes.map((node) => getTextnodeRects(node, 0, node.length)).flat();\n    const finalRects = rectToBatch(rects).map((rects) => getBiggestBoxFromBoxes(rects));\n\n    const sizePerChar = memoize(() => {\n        const numberOfChar = textNodes.reduce(\n            (acc, node) => acc + node.textContent.replaceAll(/\\s/g, \"\").length,\n            0\n        );\n        return finalRects.reduce((acc, rect) => acc + rect.width, 0) / numberOfChar;\n    });\n    const numberOfCharPerWidth = memoize((width) => Math.round(width / sizePerChar()));\n\n    const containerRect = highlightEl.getBoundingClientRect();\n    // Note: We cannot use `getClientRects()` as we want to be able to draw\n    // text highlights in the snippet/page dialogs where iframe is scaled.\n    const inPreviewIframe =\n        highlightEl.ownerDocument.documentElement.classList.contains(\"o_add_snippets_preview\");\n    const scale = inPreviewIframe ? highlightEl.offsetWidth / containerRect.width : 1;\n    const firstRect = highlightEl.getClientRects()[0];\n    const svgs = [];\n    for (const rects of finalRects) {\n        const svg = makeHighlightSvg(highlightID || getCurrentTextHighlight(highlightEl), {\n            width: rects.width * scale,\n            height: rects.height * scale,\n            numberOfCharPerWidth,\n        });\n        svgs.push(svg);\n        const spanOffsetX = firstRect.x - containerRect.x;\n        const spanOffsetY = firstRect.y - containerRect.y;\n        svg.style.left = `${(rects.x - containerRect.x - spanOffsetX) * scale}px`;\n        svg.style.top = `${(rects.y - containerRect.y - spanOffsetY) * scale}px`;\n        svg.style.bottom = `0px`;\n        svg.style.right = `0px`;\n    }\n    return svgs;\n}\nexport function applyTextHighlight(highlightEl, highlightID) {\n    const svgs = makeHighlightSvgs(highlightEl, highlightID);\n    for (const svg of svgs) {\n        highlightEl.appendChild(svg);\n    }\n}\n\n/**\n * Deactivates a text highlight effect by removing its SVGs.\n *\n * @param {HTMLElement} highlightEl\n */\nexport function removeTextHighlight(highlightEl) {\n    for (const svg of highlightEl.querySelectorAll(\":scope svg\")) {\n        svg.remove();\n    }\n}\n\n/**\n * Returns a new highlight SVG adapted to the text container.\n *\n * @param {HTMLElement} textEl\n * @param {String} highlightID\n */\nexport function makeHighlightSvg(highlightID, params) {\n    const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n    svg.setAttribute(\"fill\", \"none\");\n    svg.classList.add(\n        \"o_text_highlight_svg\",\n        // Identifies DOM content that should not be merged by the editor, even\n        // on identical parents.\n        \"o_content_no_merge\",\n        \"position-absolute\",\n        \"overflow-visible\",\n        \"pe-none\"\n    );\n    textHighlightFactory[highlightID](params).forEach((pathEl) => {\n        // pathEl.classList.add(`o_text_highlight_path_${highlightID}`);\n        svg.appendChild(pathEl);\n    });\n    return svg;\n}\n\n/**\n * Draws one or many SVG paths using templates of path shape commands.\n *\n * @param {HTMLElement} textEl\n * @param {String} options.mode Specifies how to draw the path:\n * - \"pattern\": repeat the template along the horizontal axis.\n * - \"line\": draw a simple line (we specify the width & position).\n * - \"free\": draw the path shape using the template only.\n * - \"fill\": used for irregular shapes that do not follow the \"stroke\" design.\n * @param {Function} options.template Returns a list of SVG path\n * commands adapted to the container's size.\n * @returns {String[]}\n */\nfunction drawPath(options) {\n    const { width, height, numberOfCharPerWidth } = options;\n    const yStart = options.position === \"center\" ? height / 2 : height;\n\n    switch (options.mode) {\n        case \"pattern\": {\n            let i = 0;\n            const arr = [];\n            const w = width / numberOfCharPerWidth(width),\n                h = height * 0.2;\n            while (i < numberOfCharPerWidth(width)) {\n                arr.push(options.template(w, h));\n                i++;\n            }\n            return buildPath([`M 0,${yStart} ${arr.join(\" \")}`], options);\n        }\n        case \"line\": {\n            return buildPath([`M 0,${yStart} h ${width}`], options);\n        }\n    }\n    return buildPath(options.template(width, height), options);\n}\n/**\n * Used to build the SVG <path/>, it should mainly adapt it to take into\n * consideration some cases where the shape is a \"filled path\" instead\n * of a single line stroke.\n *\n * @param {String[]} templates\n * @param {Object} options\n * @returns {Element[]}\n */\nfunction buildPath(templates, options) {\n    return templates.map((d) => {\n        const path = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n        path.setAttribute(\"stroke-width\", \"var(--text-highlight-width)\");\n        path.setAttribute(\"stroke\", \"var(--text-highlight-color)\");\n        path.setAttribute(\"stroke-linecap\", \"round\");\n        if (options.mode === \"fill\") {\n            const wScale = options.width / options.SVGWidth;\n            let hScale = options.height / options.SVGHeight;\n            const transforms = [];\n            if (options.position === \"bottom\") {\n                hScale *= 0.3;\n                transforms.push(`translate(0 ${options.height * 0.8})`);\n            }\n            transforms.push(`scale(${wScale}, ${hScale})`);\n            path.setAttribute(\"fill\", \"var(--text-highlight-color)\");\n            path.setAttribute(\"transform\", transforms.join(\" \"));\n        }\n        path.setAttribute(\"d\", d);\n        return path;\n    });\n}\n/**\n * Used to get the current text highlight id from the top `.o_text_highlight`\n * container class.\n *\n * @param {HTMLElement} el\n * @returns {String}\n */\nexport function getCurrentTextHighlight(el) {\n    const highlightEl = el.closest(\".o_text_highlight\");\n    if (!highlightEl) {\n        return;\n    }\n    return Array.from(highlightEl.classList)\n        .find((cls) => cls.startsWith(\"o_text_highlight_\"))\n        ?.replace(\"o_text_highlight_\", \"\");\n}\nfunction rectToBatch(rects) {\n    if (!rects.length) {\n        return [];\n    }\n    const rectBatches = [];\n    let lastX = rects[0].x - 1;\n    let lineIndex2 = 0;\n    for (const rect of rects) {\n        if (rect.x <= lastX) {\n            lineIndex2++;\n        }\n        lastX = rect.x;\n        rectBatches[lineIndex2] = rectBatches[lineIndex2] || [];\n        rectBatches[lineIndex2].push(rect);\n    }\n    return rectBatches;\n}\nfunction getBiggestBoxFromBoxes(includedBoxes) {\n    const firstBox = includedBoxes[0];\n    const combinedRect = new DOMRect(firstBox.x, firstBox.y, firstBox.width, firstBox.height);\n    for (const box of includedBoxes) {\n        if (box.x < combinedRect.x) {\n            combinedRect.x = box.x;\n        }\n        if (box.y < combinedRect.y) {\n            combinedRect.y = box.y;\n        }\n        if (box.bottom > combinedRect.bottom) {\n            combinedRect.height = box.bottom - combinedRect.y;\n        }\n        if (box.right > combinedRect.right) {\n            combinedRect.width = box.right - combinedRect.x;\n        }\n    }\n    return combinedRect;\n}\nfunction getTextnodeRects(el) {\n    const range = new Range();\n    range.setStart(el, 0);\n    range.setEnd(el, el.textContent.length);\n    return [...range.getClientRects()];\n}\n// todo: handle RTL\n// function isRTL(el) {\n//     return window.getComputedStyle(el).direction === \"rtl\";\n// }\n\n/**\n * Returns the closest ancestor element that should be observed for adapting\n * highlight effects.\n *\n * @param {HTMLElement} el\n * @param {HTMLElement} topEl The upper boundary element to observe (defaults\n * to document body).\n * @returns {HTMLElement}\n */\nexport function closestToObserve(el, topEl = el.ownerDocument.body) {\n    el = el.nodeType === Node.ELEMENT_NODE ? el : el.parentElement;\n    if (!el || el === topEl) {\n        return null;\n    }\n    if (window.getComputedStyle(el).display !== \"inline\") {\n        return el;\n    }\n    return closestToObserve(el.parentElement, topEl);\n}\n\n/**\n * @param {HTMLElement} el\n */\nexport function getObservedEls(el) {\n    const closestToObserveEl = closestToObserve(el);\n    return closestToObserveEl ? [closestToObserveEl, el] : [el];\n}\n", "import { UserSwitch } from \"@web/core/user_switch/user_switch\";\nimport { registry } from \"@web/core/registry\";\nexport class UserSwitchEdit extends UserSwitch {\n    static template = \"website.login_user_switch.edit\";\n}\n\nregistry.category(\"public_components.edit\").add(\"web.user_switch\", UserSwitchEdit);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class DonationForm extends Interaction {\n    static selector = \".o_donation_payment_form\";\n    dynamicContent = {\n        \".o_amount_input\": { \"t-on-focus\": this.onFocusAmountInput },\n        \"#donation_comment_checkbox\": { \"t-on-change.withTarget\": this.onDonationCommentChange },\n        \"input[type='radio']\": { \"t-on-change\": this.onSelectRadioButton },\n        \"#other_amount_value\": { \"t-on-input\": this.onChangeAmountInput },\n    };\n\n    /**\n     * @param {Event} ev\n     */\n    onFocusAmountInput(ev) {\n        const otherAmountEl = this.el.querySelector(\"#other_amount\");\n        if (otherAmountEl) {\n            otherAmountEl.checked = true;\n        }\n    }\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onDonationCommentChange(ev, currentTargetEl) {\n        const checked = currentTargetEl.checked;\n        const donationCommentEl = this.el.querySelector('#donation_comment');\n        donationCommentEl.classList.toggle('d-none', !checked);\n        if (!checked) {\n            donationCommentEl.value = \"\";\n        }\n    }\n    /**\n     * Validates the custom donation amount input field. Displays a warning if\n     * the input is empty, zero, or invalid, and also if the amount is below the\n     * minimum threshold.\n     *\n     * @param {Event} ev\n     */\n    onChangeAmountInput(ev) {\n        const inputEl = ev.currentTarget;\n        const warningMessageEl = this.el.querySelector(\"#warning_message_id\");\n        const warningMinMessageEl = this.el.querySelector(\"#warning_min_message_id\");\n        const value = parseFloat(inputEl.value);\n        warningMessageEl.classList.toggle(\"d-none\", value);\n        warningMinMessageEl.classList.toggle(\"d-none\", value ? inputEl.min <= value : true);\n    }\n    /**\n     * Handles the selection of donation amount options. If \"Other Amount\" is\n     * selected, triggers validation for custom input. Otherwise, it clears the\n     * custom amount input and hides all warnings when another option is chosen.\n     *\n     * @param {Event} ev\n     */\n    onSelectRadioButton(ev) {\n        if (ev.currentTarget.id === \"other_amount\") {\n            return this.el.querySelector(\"#other_amount_value\").focus();\n        }\n        this.el.querySelector(\"#other_amount_value\").value = \"\";\n        this.el.querySelector(\"#warning_min_message_id\").classList.add(\"d-none\");\n        this.el.querySelector(\"#warning_message_id\").classList.add(\"d-none\");\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_payment.donation_form\", DonationForm);\n", "import { _t } from '@web/core/l10n/translation';\nimport { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            'input[name=\"o_donation_amount\"]': {\n                't-on-change': this.updateAmount.bind(this),\n                't-on-focus': this.updateAmount.bind(this),\n            },\n            'input[name=\"amount\"]': { 't-on-focus': this.updateAmount.bind(this) },\n        });\n    },\n\n    // #=== EVENT HANDLERS ===#\n\n    /**\n     * Update the amount in the payment context with the user input.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    updateAmount(ev) {\n        if (ev.target.value >= 0) {\n            this.paymentContext.amount = ev.target.value;\n            const otherAmountEl = this.el.querySelector(\"#other_amount\");\n            if (ev.target.id === \"other_amount_value\" && otherAmountEl) {\n                otherAmountEl.value = ev.target.value;\n            }\n            if (ev.target.id === \"other_amount\" || ev.target.id === \"other_amount_value\") {\n                this.el.querySelectorAll('input[name=\"o_donation_amount\"][type=\"radio\"]').forEach(\n                    radioEl => radioEl.checked = false\n                );\n            } else if (ev.target.name === \"o_donation_amount\" && otherAmountEl) {\n                otherAmountEl.checked = false;\n            }\n        }\n    },\n\n    /**\n     * Checks constraints on submit:\n     * 1. The value must be greater than the minimum value.\n     * 2. A radio button must be checked, if the custom amount is selected.\n     * 3. The custom input must have a value.\n     *\n     * @override method from payment.payment_form\n     * @param {Event} ev\n     */\n    async submitForm(ev) {\n        ev.stopPropagation();\n        ev.preventDefault();\n\n        const donationAmountInputEl = this.el.querySelector(\"#other_amount_value\");\n        const otherAmountRadioEl = this.el.querySelector(\"#other_amount\");\n        const considerAmountInput = otherAmountRadioEl ? otherAmountRadioEl.checked : true;\n        if (\n            donationAmountInputEl &&\n            considerAmountInput &&\n            (!donationAmountInputEl.value || parseFloat(donationAmountInputEl.value) <= 0)\n        ) {\n            // If the warning message is already displayed, we don't need to display it again.\n            if (this.el.querySelector(\"#warning_min_message_id\").classList.contains(\"d-none\")) {\n                this.el.querySelector(\"#warning_message_id\").classList.remove(\"d-none\");\n            }\n            return donationAmountInputEl.focus();\n        }\n\n        await super.submitForm(...arguments);\n    },\n\n    // #=== PAYMENT FLOW ===#\n\n    /**\n     * Perform some validations for donations before processing the payment flow.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _initiatePaymentFlow(providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (document.querySelector('.o_donation_payment_form')) {\n            const mandatoryFields = {\n                'name': _t('Name'),\n                'email': _t('Email'),\n                'country_id': _t('Country'),\n            };\n\n            let firstInvalidFieldEl;\n            for (const id in mandatoryFields) {\n                const fieldEl = this.el.querySelector(`input[name=\"${id}\"],select[name=\"${id}\"]`);\n                const isInvalid =\n                    !fieldEl.value.trim() || (id === \"email\" && !fieldEl.checkValidity());\n                fieldEl.classList.toggle(\"is-invalid\", isInvalid);\n                if (isInvalid && !firstInvalidFieldEl) {\n                    firstInvalidFieldEl = fieldEl;\n                }\n            }\n            if (firstInvalidFieldEl) {\n                firstInvalidFieldEl.focus();\n                this._enableButton();\n                return;\n            }\n\n            // This prevents unnecessary toaster notifications on payment failure by catching the\n            // Promise.reject as we are already displaying error popup.\n            await super._initiatePaymentFlow(...arguments).catch((error) => {\n                console.log(error.data.message);\n            });\n        } else {\n            await super._initiatePaymentFlow(...arguments);\n        }\n    },\n\n    /**\n     * Add params used by the donation snippet for the RPC to the transaction route.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @return {object} The extended transaction route params.\n     */\n    _prepareTransactionRouteParams() {\n        const transactionRouteParams = super._prepareTransactionRouteParams(...arguments);\n        return document.querySelector('.o_donation_payment_form')\n            ? {\n            ...transactionRouteParams,\n            partner_id: parseInt(this.paymentContext['partnerId']),\n            currency_id: this.paymentContext['currencyId']\n                    ? parseInt(this.paymentContext['currencyId']) : null,\n            reference_prefix:this.paymentContext['referencePrefix']?.toString(),\n            partner_details: {\n                name: this.el.querySelector('input[name=\"name\"]').value,\n                email: this.el.querySelector('input[name=\"email\"]').value,\n                country_id: this.el.querySelector('select[name=\"country_id\"]').value,\n            },\n            donation_comment: this.el.querySelector('#donation_comment').value,\n            donation_recipient_email: this.el.querySelector(\n                'input[name=\"donation_recipient_email\"]'\n            ).value,\n        } : transactionRouteParams;\n    },\n\n});\n", "\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { formatCurrency } from \"@web/core/currency\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\n\nconst CUSTOM_BUTTON_EXTRA_WIDTH = 10;\n\nexport class DonationSnippet extends Interaction {\n    static selector = \".s_donation\";\n    dynamicContent = {\n        \".s_donation_btn\": {\n            \"t-on-click.withTarget\": this.onPrefilledClick,\n            \"t-att-class\": (el) => ({ \"active\": el === this.activeButtonEl }),\n        },\n        \".s_donation_donate_btn\": {\n            \"t-on-click.withTarget\": this.locked(this.onDonateClick, true),\n            \"t-att-class\": () => ({ \"o_ready_to_donate\": true }), // See TEST_01_DONATION_FIX\n        },\n        \"#s_donation_range_slider\": { \"t-on-input\": this.onRangeSliderInput },\n        \"#s_donation_amount_input\": {\n            \"t-on-input\": () => {\n                this.el.querySelector(\".o_donation_custom_btn_warning\")?.classList.add(\"d-none\");\n            },\n        },\n    };\n\n    setup() {\n        this.currency = null;\n        this.activeButtonEl = null;\n        this.rangeSliderEl = this.el.querySelector(\"#s_donation_range_slider\");\n        this.defaultAmount = this.el.dataset.defaultAmount;\n        if (!!this.rangeSliderEl) {\n            this.rangeSliderEl.value = this.defaultAmount;\n            this.setBubble();\n        }\n    }\n\n    async willStart() {\n        // TODO this is not perfect compared to 18.0: there can be a delay where\n        // the donation button does nothing because the currency is being\n        // loaded (while before it waited for the currency inside the handler).\n        // See TEST_01_DONATION_FIX which was adapted to this new behavior, as\n        // it cannot be restored in stable versions in a stable way.\n        // TODO the \"cached\" parameters has no effect: the actual cache is not\n        // initialized on the frontend side at the moment.\n        // TODO Also it should be the third param of rpc, not the second one...\n        this.currency = await rpc(\"/website/get_current_currency\", { cache: true });\n    }\n\n    start() {\n        const prefilledButtonEls = this.el.querySelectorAll(\".s_donation_btn, .s_range_bubble\");\n        for (const prefilledButtonEl of prefilledButtonEls) {\n            // Remove existing currency\n            prefilledButtonEl.querySelector(\".s_donation_currency\")?.remove();\n            const insertBefore = this.currency.position === \"before\";\n            const currencyEl = document.createElement(\"span\");\n            currencyEl.innerText = this.currency.symbol;\n            currencyEl.classList.add(\"s_donation_currency\", insertBefore ? \"pe-1\" : \"ps-1\");\n            this.insert(currencyEl, prefilledButtonEl, insertBefore ? \"afterbegin\" : \"beforeend\");\n        }\n\n        const customButtonEl = this.el.querySelector(\"#s_donation_amount_input\");\n        if (customButtonEl) {\n            this.registerCleanup(() => { customButtonEl.style.maxWidth = \"\" });\n            const canvasEl = document.createElement(\"canvas\");\n            const context = canvasEl.getContext(\"2d\");\n            context.font = window.getComputedStyle(customButtonEl).font;\n            const width = context.measureText(customButtonEl.placeholder).width;\n            customButtonEl.style.maxWidth = `${Math.ceil(width) + CUSTOM_BUTTON_EXTRA_WIDTH}px`;\n        }\n    }\n\n    setBubble() {\n        const bubbleEl = this.el.querySelector(\".s_range_bubble\");\n        const val = this.rangeSliderEl.value;\n        const min = this.rangeSliderEl.min || 0;\n        const max = this.rangeSliderEl.max || 100;\n        const newVal = Number(((val - min) * 100) / (max - min));\n        const tipOffsetLow = 8 - (newVal * 0.16); // the range thumb size is 16px*16px. The '8' and the '0.16' are related to that 16px (50% and 1% of 16px)\n\n        for (const child of bubbleEl.childNodes) {\n            if (child.nodeType === 3) {\n                child.nodeValue = val;\n            }\n        }\n        // Sorta magic numbers based on size of the native UI thumb (source: https://css-tricks.com/value-bubbles-for-range-inputs/)\n        bubbleEl.style.insetInlineStart = `calc(${newVal}% + (${tipOffsetLow}px))`;\n    }\n\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onPrefilledClick(ev, currentTargetEl) {\n        this.activeButtonEl = currentTargetEl;\n        const amountInputEl = this.el.querySelector(\"#s_donation_amount_input\");\n        if (!currentTargetEl.classList.contains(\"s_donation_custom_btn\") && amountInputEl) {\n            this.el.querySelector(\".o_donation_custom_btn_warning\")?.classList.add(\"d-none\");\n            amountInputEl.value = \"\";\n        }\n        if (this.rangeSliderEl) {\n            this.rangeSliderEl.value = this.activeButtonEl.dataset.donationValue;\n            this.setBubble();\n        }\n    }\n\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onDonateClick(ev, currentTargetEl) {\n        this.el.querySelector(\".alert-danger\")?.remove();\n        const donationButtonEls = this.el.querySelectorAll(\".s_donation_btn\");\n        let amount = this.activeButtonEl ? parseFloat(this.activeButtonEl.dataset.donationValue) : 0;\n        if (this.el.dataset.displayOptions && !amount) {\n            if (this.rangeSliderEl) {\n                amount = parseFloat(this.rangeSliderEl.value);\n            } else if (donationButtonEls.length) {\n                amount = parseFloat(this.el.querySelector(\"#s_donation_amount_input\")?.value);\n                let errorMessage = \"\";\n                const minAmount = parseFloat(this.el.dataset.minimumAmount);\n                if (!amount) {\n                    errorMessage = _t(\"Please select or enter an amount\");\n                } else if (amount < minAmount) {\n                    errorMessage = _t(\n                        \"The minimum donation amount is %(amount)s\",\n                        {\n                            amount: formatCurrency(minAmount, this.currency.id),\n                        }\n                    );\n                }\n                if (errorMessage) {\n                    const pEl = document.createElement(\"p\");\n                    pEl.classList.add(\"alert\", \"alert-danger\", \"o_donation_custom_btn_warning\");\n                    pEl.innerText = errorMessage;\n                    this.insert(pEl, currentTargetEl, \"beforebegin\");\n                    return;\n                }\n            }\n        }\n        if (!amount) {\n            amount = this.defaultAmount;\n        }\n        const formEl = this.el.querySelector(\".s_donation_form\");\n\n        const inputsParams = [\n            [\"amount\", amount],\n            [\"currency_id\", this.currency.id],\n            [\"csrf_token\", odoo.csrf_token],\n            [\"donation_options\", JSON.stringify(this.el.dataset)],\n        ];\n\n        for (const inputParams of inputsParams) {\n            const inputEl = document.createElement(\"input\");\n            inputEl.setAttribute(\"type\", \"hidden\");\n            inputEl.setAttribute(\"name\", inputParams[0]);\n            inputEl.setAttribute(\"value\", inputParams[1]);\n            this.insert(inputEl, formEl);\n        }\n\n        formEl.submit();\n    }\n\n    onRangeSliderInput() {\n        this.activeButtonEl = null;\n        this.setBubble();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_payment.donation_snippet\", DonationSnippet);\n", "import { registry } from '@web/core/registry';\nimport { Interaction } from '@web/public/interaction';\n\n\nexport class SupportedPaymentMethods extends Interaction {\n    static selector = '.s_supported_payment_methods';\n\n    setup() {\n        this.payment_methods = [];\n        this.templateKey = 'website_payment.s_supported_payment_methods.icons';\n    }\n\n    async willStart() {\n        await this.fetchPaymentMethods();\n    }\n\n    /**\n     * Fetch the payment methods and cache them in the session.\n     *\n     * Caching limits the amount of rpc calls when switching pages, or when editing the snippet in\n     * the editor as any edit reloads the interaction.\n     */\n    async fetchPaymentMethods() {\n        this.payment_methods = await this.waitFor(this.services.http.get(\n            `/website_payment/snippet/supported_payment_methods?limit=${this.limit}`\n        )).catch(_ => []);\n    }\n\n    start() {\n        this.el.replaceChildren();\n        this.renderAt(\n            this.templateKey,\n            { payment_methods: this.payment_methods, height: this.height },\n            this.el,\n        );\n    }\n\n    get limit() { return parseInt(this.el.dataset.limit) || 6; }\n\n    get height() { return parseInt(this.el.dataset.height) || 30; }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.supported_payment_methods', SupportedPaymentMethods);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { rpc } from \"@web/core/network/rpc\";\nimport { ReCaptcha } from \"@google_recaptcha/js/recaptcha\";\n\nexport class Follow extends Interaction {\n    static selector = \"#wrapwrap\";\n    static selectorHas = \".js_follow\";\n    dynamicContent = {\n        \".js_follow > .d-none\": {\n            \"t-att-class\": () => ({ \"d-none\": false }),\n        },\n        \".js_follow_btn, .js_unfollow_btn\": {\n            \"t-on-click.prevent.withTarget\": this.onToggleFollowClick,\n        },\n    };\n\n    setup() {\n        this.isUser = false;\n        this.recaptcha = new ReCaptcha();\n    }\n\n    async willStart() {\n        const jsFollowEls = this.el.querySelectorAll(\".js_follow\");\n\n        const records = {};\n        for (const jsFollowEl of jsFollowEls) {\n            const model = jsFollowEl.dataset.object;\n            if (!(model in records)) {\n                records[model] = [];\n            }\n            records[model].push(parseInt(jsFollowEl.dataset.id));\n        }\n\n        const promises = [\n            rpc(\"/website_mail/is_follower\", { records: records }),\n            this.recaptcha.loadLibs(),\n        ];\n        const [data] = await this.waitFor(Promise.all(promises));\n\n        this.isUser = data[0].is_user;\n        for (const jsFollowEl of jsFollowEls) {\n            const model = this.el.dataset.object;\n            const email = data[0].email;\n            const needToEnable = model in data[1] && data[1][model].includes(parseInt(this.el.dataset.id));\n            this.toggleSubscription(needToEnable, email, jsFollowEl);\n            jsFollowEl.classList.remove(\"d-none\");\n        }\n    }\n\n    /**\n     * Toggles subscription state for every given records.\n     *\n     * @param {boolean} follow\n     * @param {string} email\n     * @param {HTMLElement} jsFollowEl\n     */\n    toggleSubscription(follow, email, jsFollowEl) {\n        this.updateSubscriptionDOM(follow || !email && jsFollowEl.dataset.unsubscribe, email, jsFollowEl);\n    }\n\n    /**\n     * Updates subscription DOM for every given records.\n     * This should not be called directly, use `toggleSubscription`.\n     *\n     * @param {boolean} follow\n     * @param {string} email\n     * @param {HTMLElement} jsFollowEl\n     */\n    updateSubscriptionDOM(follow, email, jsFollowEl) {\n        const jsFollowEmailEl = jsFollowEl.querySelector(\"input.js_follow_email\");\n        if (jsFollowEmailEl) {\n            jsFollowEmailEl.setAttribute(\"value\", email || \"\");\n            if (email && (follow || this.isUser)) {\n                jsFollowEmailEl.setAttribute(\"disabled\", true)\n            } else {\n                jsFollowEmailEl.removeAttribute(\"disabled\")\n            }\n        }\n        jsFollowEl.dataset.follow = follow ? \"on\" : \"off\";\n    }\n\n    /**\n     * @param {HTMLElement} jsFollowEl\n     * @param {boolean} status\n     */\n    toggleEmailError(jsFollowEl, status) {\n        jsFollowEl.classList.toggle(\"o_has_error\", status)\n        const formEls = jsFollowEl.querySelectorAll(\".form-control, .form-select\")\n        for (const formEl of formEls) {\n            formEl.classList.toggle(\"is-invalid\", status);\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onToggleFollowClick(ev, currentTargetEl) {\n        const jsFollowEl = currentTargetEl.closest(\".js_follow\");\n        let email = jsFollowEl.querySelector(\".js_follow_email\");\n\n        if (email && !/.+@.+/.test(email.value)) {\n            this.toggleEmailError(jsFollowEl, true);\n            return false;\n        }\n\n        this.toggleEmailError(jsFollowEl, false);\n        email = email ? email.value : false;\n        if (email || this.isUser) {\n            const tokenCaptcha = await this.recaptcha.getToken(\"website_mail_follow\");\n            const token = tokenCaptcha.token;\n\n            if (tokenCaptcha.error) {\n                this.services.notification.add(tokenCaptcha.error, {\n                    type: \"danger\",\n                    sticky: true,\n                });\n                return false;\n            }\n\n            const turnstileCaptcha = document.querySelector(\"input[name='turnstile_captcha']\");\n            const turnstile = turnstileCaptcha ? turnstileCaptcha.value : \"\";\n\n            const data = await this.waitFor(rpc(\"/website_mail/follow\", {\n                \"id\": parseInt(jsFollowEl.dataset.id),\n                \"object\": jsFollowEl.dataset.object,\n                \"message_is_follower\": jsFollowEl.dataset.follow || \"off\",\n                \"email\": email,\n                ...(token ? { \"recaptcha_token_response\": token } : {}),\n                \"turnstile_captcha\": turnstile,\n            }));\n\n            this.toggleSubscription(data, email, jsFollowEl);\n        }\n\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_mail.follow\", Follow);\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { patchDynamicContent } from \"@web/public/utils\";\nimport { PortalComposer } from \"@portal/interactions/portal_composer\";\n\n/**\n * PortalComposer\n *\n * Extends Portal Composer to handle rating submission\n */\npatch(PortalComposer, {\n    /**\n     * @override static\n     */\n    prepareOptions(options) {\n        options = super.prepareOptions(options);\n        // apply ratio to default rating value\n        if (options.default_rating_value) {\n            options.default_rating_value = parseFloat(options.default_rating_value);\n        }\n        if (options.rating_count) {\n            options.rating_count = parseInt(options.rating_count);\n        }\n\n        // default options\n        return Object.assign({\n            \"rate_with_void_content\": false,\n            \"default_message\": false,\n            \"default_message_id\": false,\n            \"default_rating_value\": 4.0,\n            \"force_submit_url\": false,\n            \"reloadRatingPopupComposer\": (data) => { },\n        }, options);\n    },\n});\n\npatch(PortalComposer.prototype, {\n    /**\n     * @override\n     */\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            \".o-mail-Composer-stars i\": {\n                \"t-on-click.withTarget\": this.onClickStar.bind(this),\n                \"t-on-mousemove.withTarget\": this.onMoveStar.bind(this),\n                \"t-on-mouseleave\": this.onMoveLeaveStar.bind(this),\n                \"t-att-class\": (el) => {\n                    const index = Math.floor(this.starValue);\n                    const decimal = this.starValue - index;\n                    const starIndex = [...el.parentElement.children].indexOf(el) + 1; // index counts from 1 to 5\n                    return {\n                        \"fa-star-o\": starIndex > index,\n                        \"fa-star-half-o\": decimal && starIndex === index,\n                        \"fa-star\": decimal ? starIndex < index : starIndex <= index,\n                    };\n                },\n            },\n        });\n\n        this.userClick = false; // user has click or not\n        this.starValue = this.options.default_rating_value;\n        // rating stars\n        this.starListEls = this.el.querySelectorAll(\".o-mail-Composer-stars i\");\n    },\n\n    /**\n     * @override\n     */\n    start() {\n        super.start();\n        // if this is the first review, we do not use grey color contrast, even with default rating value.\n        if (!this.options.default_message_id) {\n            for (const starEl of this.starListEls) {\n                starEl.classList.remove(\"text-black-25\");\n            }\n        }\n\n        // set the default value to trigger the display of star widget and update the hidden input value.\n        this.starValue = this.options.default_rating_value;\n        this.ratingInputEl.value = this.options.default_rating_value;\n    },\n\n    get ratingInputEl() {\n        return this.el.querySelector('input[name=\"rating_value\"]');\n    },\n\n    /**\n     * @override\n     */\n    prepareMessageData() {\n        if (this.options.force_submit_url === \"/mail/message/update_content\") {\n            return {\n                hash: this.options.hash,\n                message_id: parseInt(this.options.default_message_id),\n                update_data: {\n                    attachment_ids: this.attachments.map((a) => a.id),\n                    attachment_tokens: this.attachments.map((a) => a.ownership_token),\n                    body: this.el.querySelector('textarea[name=\"message\"]').value,\n                    rating_value: this.ratingInputEl.value,\n                },\n                pid: this.options.pid,\n                token: this.options.token,\n            };\n        }\n        const res = super.prepareMessageData(...arguments)\n        res.message_id = this.options.default_message_id;\n        res.post_data.rating_value = this.ratingInputEl.value;\n        return res;\n    },\n\n    onClickStar(ev, oldFn, currentTargetEl) {\n        const index = [...currentTargetEl.parentElement.children].indexOf(currentTargetEl);\n        this.starValue = index + 1;\n        this.userClick = true;\n        this.ratingInputEl.value = this.starValue;\n    },\n\n    onMoveStar(ev, oldFn, currentTargetEl) {\n        const index = [...currentTargetEl.parentElement.children].indexOf(currentTargetEl);\n        this.starValue = index + 1;\n    },\n\n    onMoveLeaveStar() {\n        if (!this.userClick) {\n            this.starValue = parseInt(this.ratingInputEl.value);\n        }\n        this.userClick = false;\n    },\n\n    /**\n     * @override\n     */\n    async onSubmitButtonClick(ev) {\n        const result = await super.onSubmitButtonClick(...arguments);\n        const modalEl = this.el.closest(\"#ratingpopupcomposer\");\n        this.addListener(modalEl, \"hidden.bs.modal.noUpdate\", () => {\n            this.options.reloadRatingPopupComposer(result);\n        });\n        window.Modal.getOrCreateInstance(modalEl).hide();\n    },\n\n    /**\n     * @override\n     */\n    onSubmitCheckContent(ev) {\n        if (this.options.rate_with_void_content) {\n            // TODO verify comparison\n            if (this.ratingInputEl.value === \"0\") {\n                return _t(\"The rating is required. Please make sure to select one before sending your review.\")\n            }\n            return false;\n        }\n        return super.onSubmitCheckContent(...arguments);\n    },\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport { PortalComposer } from \"@portal/interactions/portal_composer\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { user } from \"@web/core/user\";\n\n/**\n * RatingPopupComposer\n *\n * Display the rating average with a static star widget, and open\n * a popup with the portal composer when clicking on it.\n **/\nexport class RatingPopupComposer extends Interaction {\n    static selector = \".o_rating_popup_composer\";\n\n    setup() {\n        const options = this.el.dataset;\n        this.rating_avg = Math.round(options[\"rating_avg\"] * 100) / 100 || 0.0;\n        this.rating_count = options[\"rating_count\"] || 0.0;\n\n        this.options = Object.assign({\n            \"token\": false,\n            \"res_model\": false,\n            \"res_id\": false,\n            \"pid\": 0,\n            \"display_rating\": true,\n            \"csrf_token\": odoo.csrf_token,\n            \"user_id\": user.userId,\n            \"reloadRatingPopupComposer\": this.onReloadRatingPopupComposer.bind(this),\n        }, options, {});\n        this.env.bus.addEventListener(\"reload_rating_popup_composer\", (ev) =>\n            this.onReloadRatingPopupComposer(ev.detail)\n        );\n    }\n\n    start() {\n        this.reloadRatingPopupComposer();\n    }\n\n    /**\n     * Destroy existing ratingPopup and insert new ratingPopup widget\n     */\n    reloadRatingPopupComposer() {\n        const starsEl = this.el.querySelector(\".o_rating_popup_composer_stars\");\n        if (this.options.hide_rating_avg) {\n            starsEl.replaceChildren();\n        } else {\n            starsEl.replaceChildren();\n            this.renderAt(\n                \"portal_rating.rating_stars_static\", {\n                inline_mode: true,\n                widget: this,\n                val: this.rating_avg,\n            }, starsEl);\n        }\n\n        // Append the modal\n        const modalEl = this.el.querySelector(\".o_rating_popup_composer_modal\");\n        modalEl.replaceChildren();\n        this.renderAt(\n            \"portal_rating.PopupComposer\", {\n            inline_mode: true,\n            widget: this,\n            val: this.rating_avg,\n        }, modalEl);\n\n        if (this.composerEl) {\n            this.services[\"public.interactions\"].stopInteractions(this.composerEl);\n        }\n\n        // Instantiate the \"Portal Composer\" widget and insert it into the modal\n        // TODO Exchange options through another mean ?\n        const options = PortalComposer.prepareOptions(this.options);\n        // Change the text of send button\n        options.send_button_label = options.default_message_id ? _t(\"Update review\") : _t(\"Post review\");\n        this.env.portalComposerOptions = options;\n        const locationEl = this.el.querySelector(\".o_rating_popup_composer_modal .o_portal_chatter_composer\");\n        // TODO maybe always put in this.options - and prepare in setup ???\n        if (!locationEl) {\n            return;\n        }\n        this.composerEl = this.renderAt(\"portal.Composer\", { widget: {options: this.env.portalComposerOptions }}, locationEl, \"afterend\")[0];\n        delete this.env.portalComposerOptions;\n        locationEl.remove();\n        // Change the text of the button\n        this.el.querySelector(\".o_rating_popup_composer_text\").textContent =\n            options.is_fullscreen\n                ? _t(\"Review\")\n                : options.default_message_id\n                    ? _t(\"Edit Review\")\n                    : _t(\"Add Review\");\n    }\n\n    /**\n     * @param {OdooEvent|Object} eventOrData\n     */\n    onReloadRatingPopupComposer(eventOrData) {\n        const data = eventOrData.data || eventOrData;\n        // Refresh the internal state of the widget\n        this.rating_avg = data.rating_avg || data[\"mail.thread\"][0].rating_avg;\n        this.rating_count = data.rating_count || data[\"mail.thread\"][0].rating_count;\n        this.rating_value = data.rating_value || data[\"rating.rating\"]?.[0].rating;\n\n        // Clean the dictionary\n        delete data.rating_avg;\n        delete data.rating_count;\n        delete data.rating_value;\n\n        this.updateOptions(data);\n        this.reloadRatingPopupComposer();\n    }\n\n    /**\n     * @param {Object} data\n     */\n    updateOptions(data) {\n        const message = data[\"mail.message\"] && data[\"mail.message\"][0];\n        const defaultOptions = {\n            default_message:\n                data.default_message || (message && message.body && message.body[1].replace(/<[^>]+>/g, \"\")),\n            default_message_id:\n                data.default_message_id ||\n                (message &&\n                    (message.body && message.body[1].replace(/<[^>]+>/g, \"\") ||\n                        message.attachment_ids.length ||\n                        message.rating_id) &&\n                    message.id),\n            default_attachment_ids: data.default_attachment_ids || data[\"ir.attachment\"],\n            default_rating_value:\n                data.default_rating_value || this.rating_value || 4,\n        };\n        Object.assign(data, defaultOptions);\n        this.options = Object.assign(this.options, data);\n    }\n}\n\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"portal_rating.rating_popup_composer\", RatingPopupComposer);\n", "import { PaymentPostProcessing } from '@payment/interactions/post_processing';\nimport { patch } from '@web/core/utils/patch';\n\npatch(PaymentPostProcessing, {\n\n    /**\n     * Don't wait for the transaction to be confirmed before redirecting customers to the landing\n     * route, because custom transactions remain in the state 'pending' forever.\n     *\n     * @override method from `@payment/interactions/post_processing`\n-    * @param {string} providerCode - The code of the provider handling the transaction.\n     */\n    getFinalStates(providerCode) {\n        const finalStates = super.getFinalStates(...arguments);\n        if (providerCode === 'custom') {\n            finalStates.add('pending');\n        }\n        return finalStates;\n    },\n\n});\n", "import { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n\n    /**\n     * Configure 'cash_on_delivery' as a pay later method.\n     *\n     * @override\n     */\n    _isPayLaterPaymentMethod(paymentMethodCode) {\n        return (\n            paymentMethodCode === 'cash_on_delivery'\n            || super._isPayLaterPaymentMethod(...arguments)\n        );\n    }\n\n});\n", "import {\n    LocationSchedule\n} from '@delivery/js/location_selector/location_schedule/location_schedule';\nimport { Component } from '@odoo/owl';\nimport { _t } from '@web/core/l10n/translation';\n\nexport class Location extends Component {\n    static components = { LocationSchedule };\n    static template = 'delivery.locationSelector.location';\n    static props = {\n        id: String,\n        number: Number,\n        name: String,\n        street: String,\n        city: String,\n        zipCode: String,\n        openingHours: {\n            type: Object,\n            values: {\n                type: Array,\n                element: String,\n                optional: true,\n            },\n        },\n        additionalData: { type: Object, optional: true },\n        isSelected: Boolean,\n        setSelectedLocation: Function,\n    };\n\n    /**\n     * Get the city and the zip code.\n     *\n     * @return {Object} The city and the zip code.\n     */\n    getCityAndZipCode() {\n        return `${this.props.zipCode} ${this.props.city}`;\n    }\n\n    get openingHoursLabel() {\n        return _t(\"Opening hours\");\n    }\n}\n", "import { Location } from '@delivery/js/location_selector/location/location';\nimport { Component, onMounted, useEffect } from '@odoo/owl';\n\nexport class LocationList extends Component {\n    static components = { Location };\n    static template = 'delivery.locationSelector.locationList';\n    static props = {\n        locations: {\n            type: Array,\n            element: {\n                type: Object,\n                values: {\n                    id: String,\n                    name: String,\n                    openingHours: {\n                        type: Object,\n                        values: {\n                            type: Array,\n                            element: String,\n                            optional: true,\n                        },\n                    },\n                    street: String,\n                    city: String,\n                    zip_code: String,\n                    state: { type: String, optional: true},\n                    country_code: String,\n                    additional_data: { type: Object, optional: true},\n                    latitude: String,\n                    longitude: String,\n                }\n            },\n        },\n        selectedLocationId: [String, {value: false}],\n        setSelectedLocation: Function,\n        validateSelection: Function,\n    };\n\n    setup() {\n        onMounted(() => {\n            document.getElementById(`location-${this.props.selectedLocationId}`).focus();\n        });\n\n        // Focus on the location on the list when clicking on the map marker.\n        useEffect(\n            (locations, selectedLocationId) => {\n                const selectedLocation = locations.find(\n                    l => String(l.id) === selectedLocationId\n                );\n                if (selectedLocation) {\n                    document.getElementById(`location-${selectedLocation.id}`).focus();\n                }\n            },\n            () => [this.props.locations, this.props.selectedLocationId]\n        );\n    }\n}\n", "import { Component } from '@odoo/owl';\nimport { _t } from '@web/core/l10n/translation';\n\nexport class LocationSchedule extends Component {\n    static template = 'delivery.locationSelector.schedule';\n    static props = {\n        openingHours: {\n            type: Object,\n            values: {\n                type: Array,\n                element: String,\n                optional: true,\n            },\n        },\n        wrapClass: { type: String, optional: true },\n    };\n\n    /**\n     * Return the localized day's name given his index in the week.\n     *\n     * @param {Number} weekday - The number of the day of the week. 0 for Monday, 6 for Sunday.\n     * @return {Object} the localized name of the day (long version).\n     */\n    getWeekDay(weekday) {\n        return luxon.Info.weekdays()[weekday]\n    }\n\n    get closedLabel() {\n        return _t(\"Closed\");\n    }\n}\n", "import { LocationList } from '@delivery/js/location_selector/location_list/location_list';\nimport { MapContainer } from '@delivery/js/location_selector/map_container/map_container';\nimport { Component, onMounted, onWillUnmount, useEffect, useState } from '@odoo/owl';\nimport { browser } from '@web/core/browser/browser';\nimport { Dialog } from '@web/core/dialog/dialog';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport { useDebounced } from '@web/core/utils/timing';\n\nexport class LocationSelectorDialog extends Component {\n    static components = { Dialog, LocationList, MapContainer };\n    static template = 'delivery.locationSelector.dialog';\n    static props = {\n        orderId: Number,\n        zipCode: String,\n        selectedLocationId: { type: String, optional: true},\n        save: Function,\n        close: Function, // This is the close from the env of the Dialog Component\n    };\n    static defaultProps = {\n        selectedLocationId: false,\n    };\n\n    setup() {\n        this.state = useState({\n            locations: [],\n            error: false,\n            viewMode: 'list',\n            zipCode: this.props.zipCode,\n            // Some APIs like FedEx use strings to identify locations.\n            selectedLocationId: String(this.props.selectedLocationId),\n            isSmall: this.env.isSmall,\n        });\n\n        this.getLocationUrl = '/delivery/get_pickup_locations';\n\n        this.debouncedOnResize = useDebounced(this.updateSize, 300);\n        this.debouncedSearchButton = useDebounced((zipCode) => {\n            this.state.locations = [];\n            this._updateLocations(zipCode);\n        }, 300);\n\n        onMounted(() => {\n            browser.addEventListener('resize', this.debouncedOnResize);\n            this.updateSize();\n        });\n        onWillUnmount(() => browser.removeEventListener('resize', this.debouncedOnResize));\n\n        // Fetch new locations when the zip code is updated.\n        useEffect(\n            (zipCode) => {\n                this._updateLocations(zipCode)\n                return () => {\n                    this.state.locations = []\n                };\n            },\n            () => [this.state.zipCode]\n        );\n    }\n\n    //--------------------------------------------------------------------------\n    // Data Exchanges\n    //--------------------------------------------------------------------------\n\n    /**\n     * Fetch the closest pickup locations based on the zip code.\n     *\n     * @private\n     * @param {String} zip - The zip code used to look for close locations.\n     * @return {Object} The result values.\n     */\n    async _getLocations(zip) {\n        return rpc(this.getLocationUrl, {order_id: this.props.orderId, zip_code: zip});\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Get the locations based on the zip code.\n     *\n     * Select the first location available if no location is currently selected or if the currently\n     * selected location is not on the list anymore.\n     *\n     * @private\n     * @param {String} zip - The zip code used to look for close locations.\n     * @return {void}\n     */\n    async _updateLocations(zip) {\n        this.state.error = false;\n        const { pickup_locations, error } = await this._getLocations(zip);\n        if (error) {\n            this.state.error = error;\n            console.error(error);\n        } else {\n            this.state.locations = pickup_locations;\n            if (!this.state.locations.find(l => String(l.id) === this.state.selectedLocationId)) {\n                this.state.selectedLocationId = this.state.locations[0]\n                                                ? String(this.state.locations[0].id)\n                                                : false;\n            }\n        }\n    }\n\n    get showListView() {\n        return this.state.locations.length !== 1;\n    }\n\n    /**\n     * Find the selected location based on its id.\n     *\n     * @return {Object} The selected location.\n     */\n    get selectedLocation() {\n        return this.state.locations.find(l => String(l.id) === this.state.selectedLocationId);\n    }\n\n    /**\n     * Set the selectedLocationId in the state.\n     *\n     * @param {String} locationId\n     * @return {void}\n     */\n    setSelectedLocation(locationId) {\n        this.state.selectedLocationId = String(locationId);\n    }\n\n    /**\n     * Confirm the current selected location.\n     *\n     * @return {void}\n     */\n    async validateSelection() {\n        if (!this.state.selectedLocationId) return;\n        const selectedLocation = this.state.locations.find(\n            l => String(l.id) === this.state.selectedLocationId\n        );\n        await this.props.save(selectedLocation);\n        this.props.close();\n    }\n\n    //--------------------------------------------------------------------------\n    // User Interface\n    //--------------------------------------------------------------------------\n\n    /**\n     * Determines the component to show in mobile view based on the current state.\n     *\n     * Returns the MapContainer component if `viewMode` is strictly equal to `map`, else return the\n     * List component.\n     *\n     * @return {Component} The component to show in mobile view.\n     */\n    get mobileComponent() {\n        if (this.state.viewMode === 'map') return MapContainer;\n        return LocationList;\n    }\n\n    get title() {\n        if (this.state.locations.length === 1) {\n            return _t(\"Pickup Location\")\n        }\n        return _t(\"Choose a pick-up point\");\n    }\n\n    get validationButtonLabel() {\n        return _t(\"Choose this location\");\n    }\n\n    get postalCodePlaceholder() {\n        return _t(\"Your postal code\");\n    }\n\n    get listViewButtonLabel() {\n        return _t(\"List view\");\n    }\n\n    get mapViewButtonLabel() {\n        return _t(\"Map view\");\n    }\n\n    get errorMessage() {\n        return _t(\"No result\");\n    }\n\n    get loadingMessage() {\n        return _t(\"Loading...\");\n    }\n\n    /**\n     *\n     * @return {void}\n     */\n    updateSize() {\n        this.state.isSmall = this.env.isSmall;\n    }\n}\n", "/*global L*/\n\nimport { Component, useEffect, useRef } from '@odoo/owl';\nimport { renderToString } from '@web/core/utils/render';\n\nexport class Map extends Component {\n    static template = 'delivery.locationSelector.map';\n    static props = {\n        locations: {\n            type: Array,\n            element: {\n                type: Object,\n                values: {\n                    id: String,\n                    name: String,\n                    openingHours: {\n                        type: Object,\n                        values: {\n                            type: Array,\n                            element: String,\n                            optional: true,\n                        },\n                    },\n                    street: String,\n                    city: String,\n                    zip_code: String,\n                    state: { type: String, optional: true},\n                    country_code: String,\n                    additional_data: { type: Object, optional: true},\n                    latitude: String,\n                    longitude: String,\n                }\n            },\n        },\n        selectedLocationId: [String, {value: false}],\n        setSelectedLocation: Function,\n    };\n\n    setup() {\n        this.leafletMap = null;\n        this.markers = [];\n        this.mapRef = useRef('map');\n\n        // Create the map.\n        useEffect(\n            () => {\n                this.leafletMap = L.map(this.mapRef.el, {\n                    zoom: 13,\n                });\n                this.leafletMap.attributionControl.setPrefix(\n                    '<a href=\"https://leafletjs.com\" title=\"A JavaScript library for interactive maps\">Leaflet</a>'\n                );\n                L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {\n                    maxZoom: 19,\n                    attribution: \"&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>\"\n                }).addTo(this.leafletMap);\n                return () => {\n                    this.leafletMap.remove();\n                }\n            },\n            () => []\n        );\n\n        // Update the size of the map.\n        useEffect(\n            (locations) => {\n                this.leafletMap.invalidateSize();\n            },\n            () => [this.props.locations]\n        );\n\n        // Update the markers and center the map on the selected location.\n        useEffect(\n            (locations, selectedLocationId) => {\n                this.addMarkers(locations);\n                const selectedLocation = locations.find(\n                    l => String(l.id) === selectedLocationId\n                );\n                if (selectedLocation) {\n                    // Center the Map.\n                    this.leafletMap.panTo(\n                        [selectedLocation.latitude, selectedLocation.longitude],\n                        { animate: true }\n                    );\n                }\n                return () => {\n                    this.removeMarkers();\n                };\n            },\n            () => [this.props.locations, this.props.selectedLocationId]\n        );\n    }\n\n    /**\n     * Add the markers of the closest locations on the map.\n     * Binds events to the created markers.\n     *\n     * @param {Array} locations - The list of locations to display on the map.\n     * @return {void}\n     */\n    addMarkers(locations) {\n        for (const loc of locations) {\n            const isSelected = String(loc.id) === this.props.selectedLocationId\n            // Icon creation\n            const iconInfo = {\n                className: isSelected ? 'o_location_selector_marker_icon_selected'\n                                      : 'o_location_selector_marker_icon',\n                html: renderToString(\n                    'delivery.locationSelector.map.marker',\n                    { number: locations.indexOf(loc) + 1 },\n                ),\n                iconSize: [30, 40],\n                iconAnchor: [15, 40],\n            };\n\n            const marker = L.marker(\n                [ loc.latitude, loc.longitude ],\n                {\n                    icon: L.divIcon(iconInfo),\n                    title: locations.indexOf(loc) + 1,\n                },\n            );\n\n            // By default, the marker's zIndex is based on its latitude. This ensures the selected\n            // marker is always displayed on top of all others.\n            if (isSelected) marker.setZIndexOffset(100);\n\n            marker.addTo(this.leafletMap);\n            marker.addEventListener('click', () => {\n                this.props.setSelectedLocation(loc.id);\n            });\n\n            this.markers.push(marker);\n        }\n    }\n\n    /**\n     * Remove the markers from the map and empty the markers array.\n     *\n     * @return {void}\n     */\n    removeMarkers() {\n        for (const marker of this.markers) {\n            marker.removeEventListener();\n            this.leafletMap.removeLayer(marker);\n        }\n        this.markers = [];\n    }\n\n    /**\n     * Find the selected location based on its id.\n     *\n     * @return {Object} The selected location.\n     */\n    get selectedLocation() {\n        return this.props.locations.find(l => String(l.id) === this.props.selectedLocationId)\n    }\n}\n", "import {\n    LocationSchedule\n} from '@delivery/js/location_selector/location_schedule/location_schedule';\nimport { Map } from '@delivery/js/location_selector/map/map';\nimport { Component, onWillStart, useState } from '@odoo/owl';\nimport { AssetsLoadingError, loadCSS, loadJS } from '@web/core/assets';\nimport { _t } from '@web/core/l10n/translation';\n\nexport class MapContainer extends Component {\n    static components = { LocationSchedule, Map };\n    static template = 'delivery.locationSelector.mapContainer';\n    static props = {\n        locations: {\n            type: Array,\n            element: {\n                type: Object,\n                values: {\n                    id: String,\n                    name: String,\n                    openingHours: {\n                        type: Object,\n                        values: {\n                            type: Array,\n                            element: String,\n                            optional: true,\n                        },\n                    },\n                    street: String,\n                    city: String,\n                    zip_code: String,\n                    state: { type: String, optional: true},\n                    country_code: String,\n                    additional_data: { type: Object, optional: true},\n                    latitude: String,\n                    longitude: String,\n                }\n            },\n        },\n        selectedLocationId: [String, {value: false}],\n        setSelectedLocation: Function,\n        validateSelection: Function,\n    };\n\n    setup() {\n        this.state = useState({\n            shouldLoadMap: false,\n        });\n\n        onWillStart(async () => {\n            /**\n             * We load the script for the map before rendering the owl component to avoid a\n             * UserError if the script can't be loaded (e.g. if the customer loses the connection\n             * between the rendering of the page and when he opens the location selector, or if the\n             * CDN\u2019s doesn't host the library anymore).\n             */\n            try {\n                await Promise.all([\n                    loadJS('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'),\n                    loadCSS('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'),\n                ])\n                this.state.shouldLoadMap = true;\n            } catch (error) {\n                if (!(error instanceof AssetsLoadingError)) {\n                    throw error;\n                }\n            }\n        });\n    }\n\n    /**\n     * Get the city and the zip code.\n     *\n     * @param {Number} selectedLocation - The location form which the city and the zip code\n     *                                    should be taken.\n     * @return {Object} The city and the zip code.\n     */\n    getCityAndZipCode(selectedLocation) {\n        return `${selectedLocation.zip_code} ${selectedLocation.city}`;\n    }\n\n    /**\n     * Find the selected location based on its id.\n     *\n     * @return {Object} The selected location.\n     */\n    get selectedLocation() {\n        return this.props.locations.find(l => String(l.id) === this.props.selectedLocationId);\n    }\n\n    get errorMessage() {\n        return _t(\"There was an error loading the map\");\n    }\n\n    get chooseLocationButtonLabel() {\n        return _t(\"Choose this location\");\n    }\n\n    get openingHoursLabel() {\n        return _t(\"Opening hours\");\n    }\n}\n", "import { CustomerAddress } from '@portal/interactions/address';\nimport { patch } from '@web/core/utils/patch';\n\npatch(CustomerAddress.prototype, {\n    // /shop/address\n\n    setup() {\n        super.setup();\n        // There is two main buttons in the DOM for mobile or desktop. User can switch from one mode\n        // to the other by rotating their tablet.\n        this.submitButtons = document.getElementsByName(\"website_sale_main_button\");\n        if (this.submitButtons) {\n            this._boundSaveAddress = this.saveAddress.bind(this);\n            this.submitButtons.forEach(\n                submitButton => submitButton.addEventListener('click', this._boundSaveAddress)\n            );\n        }\n    },\n\n    destroy() {\n        if (this.submitButtons) {\n            this.submitButtons.forEach(\n                submitButton => submitButton.removeEventListener('click', this._boundSaveAddress)\n            );\n        }\n        super.destroy();\n    },\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { SIZES, utils as uiUtils } from \"@web/core/ui/ui_service\";\n\nexport class CarouselProduct extends Interaction {\n    static selector = \"#o-carousel-product\";\n    dynamicContent = {\n        _root: {\n            \"t-on-slide.bs.carousel.noUpdate\": this.onSlideCarouselProduct,\n            \"t-att-style\": () => ({\n                \"top\": this.top,\n            }),\n        },\n        _window: {\n            \"t-on-resize.noUpdate\": this.throttled(this.onSlideCarouselProduct),\n        },\n        \".carousel-indicators\": {\n            \"t-att-style\": () => ({\n                \"justify-content\": this.indicatorJustify,\n            }),\n        },\n        \".o_carousel_product_indicators\": {\n            \"t-on-wheel.prevent\": this.onMouseWheel,\n        },\n    };\n\n    setup() {\n        this.top = undefined;\n        this.indicatorJustify = \"start\";\n    }\n\n    start() {\n        this.updateCarouselPosition();\n        this.registerCleanup(this.services.website_menus.registerCallback(this.updateCarouselPosition.bind(this)));\n        if (this.el.querySelector(\".carousel-indicators\")) {\n            this.updateJustifyContent();\n        }\n    }\n\n    updateCarouselPosition() {\n        let size = 5;\n        for (const el of document.querySelectorAll(\".o_top_fixed_element\")) {\n            const style = window.getComputedStyle(el);\n            size += el.getBoundingClientRect().height + parseFloat(style.marginTop) + parseFloat(style.marginBottom);\n        }\n        this.top = size;\n    }\n\n    /**\n     * Center the selected indicator to scroll the indicators list when it\n     * overflows.\n     *\n     * @param {Event} ev\n     */\n    onSlideCarouselProduct(ev) {\n        const isReversed = this.el.style[\"flex-direction\"] === \"column-reverse\";\n        const isLeftIndicators = this.el.classList.contains(\"o_carousel_product_left_indicators\");\n        const indicatorsDivEl = this.el.querySelector(isLeftIndicators ? \".o_carousel_product_indicators\" : \".carousel-indicators\");\n        if (!indicatorsDivEl) {\n            return;\n        }\n        const isVertical = isLeftIndicators && !isReversed;\n        const currentIndicatorEl = ev?.relatedTarget || this.el.querySelector(\"li.active\");\n        let indicatorIndex = currentIndicatorEl ? [...currentIndicatorEl.parentElement.children].findIndex(el => el === currentIndicatorEl) : -1;\n        const indicatorEl = indicatorsDivEl.querySelector(`[data-bs-slide-to=\"${indicatorIndex}\"]`);\n        const indicatorsDivRect = indicatorsDivEl.getBoundingClientRect();\n        const indicatorsDivStyle = window.getComputedStyle(indicatorsDivEl);\n        const indicatorsDivSize = isVertical ? indicatorsDivRect.height + parseFloat(indicatorsDivStyle.marginTop) + parseFloat(indicatorsDivStyle.marginBottom) : indicatorsDivRect.width + parseFloat(indicatorsDivStyle.marginLeft) + parseFloat(indicatorsDivStyle.marginRight);\n        const indicatorRect = indicatorEl.getBoundingClientRect();\n        const indicatorStyle = window.getComputedStyle(indicatorEl);\n        const indicatorSize = isVertical ? indicatorRect.height : indicatorRect.width;\n        const indicatorPosition = isVertical ? indicatorRect.top - indicatorsDivRect.top - parseFloat(indicatorStyle.marginTop) : indicatorRect.left - indicatorsDivRect.left - parseFloat(indicatorStyle.marginLeft);\n        const scrollSize = isVertical ? indicatorsDivEl.scrollHeight : indicatorsDivEl.scrollWidth;\n        let indicatorsPositionDiff = (indicatorPosition + (indicatorSize / 2)) - (indicatorsDivSize / 2);\n        indicatorsPositionDiff = Math.min(indicatorsPositionDiff, scrollSize - indicatorsDivSize);\n        this.updateJustifyContent();\n        const indicatorsPositionX = isVertical ? \"0\" : \"-\" + indicatorsPositionDiff;\n        const indicatorsPositionY = isVertical ? \"-\" + indicatorsPositionDiff : \"0\";\n        const translate3D = indicatorsPositionDiff > 0 ? \"translate3d(\" + indicatorsPositionX + \"px,\" + indicatorsPositionY + \"px,0)\" : \"\";\n        indicatorsDivEl.style.setProperty(\"transform\", translate3D);\n    }\n\n    updateJustifyContent() {\n        this.indicatorJustify = \"start\";\n        if (uiUtils.getSize() <= SIZES.MD) {\n            const indicatorsDivEl = this.el.querySelector(\".carousel-indicators\");\n            const firstIndicatorEl = indicatorsDivEl.firstElementChild;\n            const lastIndicatorEl = indicatorsDivEl.lastElementChild;\n            const { left: lastIndicatorLeft } = lastIndicatorEl.getBoundingClientRect();\n            if (lastIndicatorLeft + firstIndicatorEl.offsetWidth < indicatorsDivEl.offsetWidth) {\n                this.indicatorJustify = \"center\";\n            }\n        }\n        this.updateContent();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onMouseWheel(ev) {\n        const bsCarousel = window.Carousel.getOrCreateInstance(this.el);\n        if (ev.deltaY > 0) {\n            bsCarousel.next();\n        } else {\n            bsCarousel.prev();\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_sale.carousel_product\", CarouselProduct);\n\nregistry\n    .category(\"public.interactions.edit\")\n    .add(\"website_sale.carousel_product\", {\n        Interaction: CarouselProduct,\n    });\n", "import { Interaction } from '@web/public/interaction';\nimport { browser } from '@web/core/browser/browser';\nimport { registry } from '@web/core/registry';\nimport { rpc } from '@web/core/network/rpc';\nimport { redirect } from '@web/core/utils/urls';\nimport wSaleUtils from '@website_sale/js/website_sale_utils';\n\nexport class CartLine extends Interaction {\n    static selector = '.o_cart_product';\n    dynamicContent = {\n        '.css_quantity > input.js_quantity': {\n            't-on-change.withTarget': this.locked(this.debounced(this.changeQuantity, 500)),\n        },\n        '.css_quantity > a': {\n            't-on-click.prevent.withTarget': this.locked(this.incOrDecQuantity),\n        },\n        '.js_delete_product': { 't-on-click.prevent': this.locked(this.deleteProduct) },\n    };\n\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async changeQuantity(ev, currentTargetEl) {\n        await this._changeQuantity(currentTargetEl);\n    }\n\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async incOrDecQuantity(ev, currentTargetEl) {\n        const input = currentTargetEl.closest('.css_quantity').querySelector('input.js_quantity');\n        const maxQuantity = parseFloat(input.dataset.max || Infinity);\n        const oldQuantity = parseFloat(input.value || 0);\n        const newQuantity = currentTargetEl.querySelector('i').classList.contains('oi-minus')\n            ? Math.min(Math.max(oldQuantity - 1, 0), maxQuantity)\n            : Math.min(oldQuantity + 1, maxQuantity);\n        if (oldQuantity !== newQuantity) {\n            input.value = newQuantity;\n            await this._changeQuantity(input);\n        }\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    async deleteProduct(ev) {\n        const input = ev.currentTarget.closest('.o_cart_product')\n            .querySelector('.css_quantity > input.js_quantity');\n        input.value = 0;\n        await this._changeQuantity(input);\n    }\n\n    async _changeQuantity(input) {\n        let quantity = parseInt(input.value || 0);\n        if (isNaN(quantity)) quantity = 1;\n        const lineId = parseInt(input.dataset.lineId);\n        const data = await this.waitFor(rpc('/shop/cart/update', {\n            line_id: lineId,\n            product_id: parseInt(input.dataset.productId),\n            quantity: quantity,\n        }));\n\n        if (!data.cart_quantity) {\n            // Ensure the last cart removal is recorded.\n            browser.sessionStorage.setItem('website_sale_cart_quantity', 0);\n            return redirect('/shop/cart');\n        }\n        input.value = data.quantity;\n        this.el.querySelectorAll(`.js_quantity[data-line-id=\"${lineId}\"]`).forEach(input =>\n            input.value = data.quantity\n        );\n\n        const cart = this.el.closest('#shop_cart');\n        // `updateCartNavBar` regenerates the cart lines and `updateQuickReorderSidebar`\n        // regenerates the quick reorder products, so we need to stop and start interactions\n        // to make sure the regenerated cart lines and reorder products are properly handled.\n        this.services['public.interactions'].stopInteractions(cart);\n        wSaleUtils.updateCartNavBar(data);\n        wSaleUtils.updateQuickReorderSidebar(data);\n        this.services['public.interactions'].startInteractions(cart);\n        wSaleUtils.showWarning(data.warning);\n        // Propagate the change to the express checkout forms.\n        this.env.bus.trigger('cart_amount_changed', [data.amount, data.minor_amount]);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.cart_line', CartLine);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class CartSuggestion extends Interaction {\n    static selector = '[name=\"suggested_product\"]';\n    dynamicContent = {\n        'button.js_add_suggested_products': { 't-on-click': this.addSuggestedProduct },\n    };\n\n    /**\n     * @param {Event} ev\n     */\n    addSuggestedProduct(ev) {\n        const dataset = ev.currentTarget.dataset;\n        this.services['cart'].add({\n            productTemplateId: parseInt(dataset.productTemplateId),\n            productId: parseInt(dataset.productId),\n            isCombo: dataset.productType === 'combo',\n        }, {\n            isBuyNow: true,\n            showQuantity: Boolean(dataset.showQuantity),\n        });\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.cart_suggestion', CartSuggestion);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport {\n    LocationSelectorDialog\n} from '@delivery/js/location_selector/location_selector_dialog/location_selector_dialog';\n\nexport class Checkout extends Interaction {\n    static selector = '#shop_checkout';\n    dynamicContent = {\n        // Addresses\n        '.card': { 't-on-click': this.changeAddress },\n        // Cancel the address change to allow the redirect to the edit page to take place.\n        '.js_edit_address': { 't-on-click.stop': () => {} },\n        '#use_delivery_as_billing': { 't-on-change': this.toggleBillingAddressRow },\n        // Delivery methods\n        '[name=\"o_delivery_radio\"]': { 't-on-click': this.selectDeliveryMethod },\n        '[name=\"o_pickup_location_selector\"]': { 't-on-click': this.selectPickupLocation },\n    };\n\n    setup() {\n        // There are two main buttons in the DOM (one for mobile and one for desktop).\n        // We need to get the one that's actually displayed.\n        this.mainButton = Array.from(document.getElementsByName('website_sale_main_button'))\n            .find(button => button.offsetParent !== null);\n        this.useDeliveryAsBillingToggle = document.querySelector('#use_delivery_as_billing');\n        this.billingContainer = this.el.querySelector('#billing_container');\n        this.addBillingAddressBtn = this.el.querySelector('.o_add_billing_address_btn');\n    }\n\n    async willStart() {\n        await this.waitFor(this._prepareDeliveryMethods());\n    }\n\n    async start() {\n        // Monitor when the page is restored from the bfcache.\n        const boundOnNavigationBack = this._onNavigationBack.bind(this);\n        window.addEventListener(\"pageshow\", boundOnNavigationBack);\n        this.registerCleanup(() => window.removeEventListener(\"pageshow\", boundOnNavigationBack));\n    }\n\n    /**\n     * Reload the page when the page is restored from the bfcache.\n     *\n     * @param {PageTransitionEvent} event - The pageshow event.\n     * @private\n     */\n    _onNavigationBack(event) {\n        if (event.persisted) {\n            window.location.reload();\n        }\n    }\n\n    /**\n     * Set the billing or delivery address on the order and update the corresponding card.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async changeAddress(ev) {\n        const newAddress = ev.currentTarget;\n        if (newAddress.classList.contains('bg-400')) { // If the card is already selected.\n            return;\n        }\n        const addressType = newAddress.dataset.addressType;\n\n        // Remove the highlighting from the previously selected address card.\n        const previousAddress = this._getSelectedAddress(addressType);\n        this._tuneDownAddressCard(previousAddress);\n\n        // Highlight the newly selected address card.\n        this._highlightAddressCard(newAddress);\n        const selectedPartnerId = newAddress.dataset.partnerId;\n        await this.waitFor(this.updateAddress(addressType, selectedPartnerId));\n        // A delivery address is changed.\n        if (addressType === 'delivery' || this.billingContainer.dataset.deliveryAddressDisabled) {\n            if (this.billingContainer.dataset.deliveryAddressDisabled) {\n                // If a delivery address is disabled in the settings, use a billing address as\n                // a delivery one.\n                await this.waitFor(this.updateAddress('delivery', selectedPartnerId));\n            }\n            if (this.useDeliveryAsBillingToggle?.checked) {\n                await this.waitFor(this._selectMatchingBillingAddress(selectedPartnerId));\n            }\n            const deliveryFormHtml = await this.waitFor(rpc('/shop/delivery_methods'));\n            // The delivery methods are regenerated below, so we need to stop and start interactions\n            // to make sure the regenerated delivery methods are properly handled.\n            this.services['public.interactions'].stopInteractions(this.el);\n            // Update the available delivery methods.\n            document.getElementById('o_delivery_form').innerHTML = deliveryFormHtml;\n            this.services['public.interactions'].startInteractions(this.el);\n            await this.waitFor(this._prepareDeliveryMethods());\n        }\n        this._enableMainButton();  // Try to enable the main button.\n    }\n\n    /**\n     * Show/hide the billing address row when the user toggles the 'use delivery as billing' input.\n     *\n     * The URLs of the \"create address\" buttons are updated to propagate the value of the input.\n     *\n     * @param ev\n     * @return {void}\n     */\n    async toggleBillingAddressRow(ev) {\n        const useDeliveryAsBilling = ev.target.checked;\n\n        const addDeliveryAddressButton = this.el.querySelector(\n            '.o_address_card_add_new[data-address-type=\"delivery\"]'\n        );\n        if (addDeliveryAddressButton) {  // If `Add address` button for delivery.\n            // Update the `use_delivery_as_billing` query param for a new delivery address URL.\n            const addDeliveryUrl = new URL(addDeliveryAddressButton.href);\n            addDeliveryUrl.searchParams.set(\n                'use_delivery_as_billing', encodeURIComponent(useDeliveryAsBilling)\n            );\n            addDeliveryAddressButton.href = addDeliveryUrl.toString();\n        }\n\n        // Toggle the billing address row.\n        if (useDeliveryAsBilling) {\n            this.billingContainer.classList.add('d-none');  // Hide the billing address row.\n            const selectedDeliveryAddress = this._getSelectedAddress('delivery');\n            await this.waitFor(\n                this._selectMatchingBillingAddress(selectedDeliveryAddress.dataset.partnerId)\n            );\n        } else {\n            this._disableMainButton();\n            this.billingContainer.classList.remove('d-none'); // Show the billing address row.\n        }\n        this.addBillingAddressBtn.classList.toggle('d-none', useDeliveryAsBilling);\n\n        this._enableMainButton();  // Try to enable the main button.\n    }\n\n    /**\n     * Fetch the delivery rate for the selected delivery method and update the displayed amounts.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async selectDeliveryMethod(ev) {\n        const checkedRadio = ev.currentTarget;\n        if (checkedRadio.disabled) {  // The delivery rate request failed.\n            return; // Failing delivery methods cannot be selected.\n        }\n\n        // Disable the main button while fetching delivery rates.\n        this._disableMainButton();\n\n        // Hide and reset the order location name and address if defined.\n        this._hidePickupLocation();\n\n        // Fetch delivery rates and update the cart summary and the price badge accordingly.\n        await this.waitFor(this._updateDeliveryMethod(checkedRadio));\n\n        // Re-enable the main button after delivery rates have been fetched.\n        this._enableMainButton();\n\n        // Show a button to open the location selector if required for the selected delivery method.\n        await this._showPickupLocation(checkedRadio);\n    }\n\n    /**\n     * Fetch and display the closest pickup locations based on the zip code.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async selectPickupLocation(ev) {\n        const { zipCode, locationId } = ev.currentTarget.dataset;\n        const deliveryMethodContainer = this._getDeliveryMethodContainer(ev.currentTarget);\n        this.services.dialog.add(LocationSelectorDialog, {\n            zipCode: zipCode,\n            selectedLocationId: locationId,\n            isFrontend: true,\n            save: async location => {\n                const jsonLocation = JSON.stringify(location);\n                // Assign the selected pickup location to the order.\n                await this.waitFor(this._setPickupLocation(jsonLocation));\n\n                //  Show and set the order location details.\n                this._updatePickupLocation(deliveryMethodContainer, location, jsonLocation);\n\n                this._enableMainButton();\n            },\n        });\n    }\n\n    // #=== DOM MANIPULATION ===#\n\n    /**\n     * Update the pickup location address elements and the 'edit' button's values.\n     *\n     * @private\n     * @param deliveryMethodContainer - The container element of the delivery method.\n     * @param location - The selected location as an object.\n     * @param jsonLocation - The selected location as an JSON string.\n     * @return {void}\n     */\n    _updatePickupLocation(deliveryMethodContainer, location, jsonLocation) {\n        const pickupLocation = deliveryMethodContainer.querySelector('[name=\"o_pickup_location\"]');\n        pickupLocation.querySelector('[name=\"o_pickup_location_name\"]').innerText = location.name;\n        pickupLocation.querySelector(\n            '[name=\"o_pickup_location_address\"]'\n        ).innerText = `${location.street} ${location.zip_code} ${location.city}`;\n        const editPickupLocationButton = pickupLocation.querySelector(\n            'span[name=\"o_pickup_location_selector\"]'\n        );\n        editPickupLocationButton.dataset.locationId = location.id;\n        editPickupLocationButton.dataset.zipCode = location.zip_code;\n        editPickupLocationButton.dataset.pickupLocationData = jsonLocation;\n        pickupLocation.querySelector(\n            '[name=\"o_pickup_location_details\"]'\n        ).classList.remove('d-none');\n\n        // Remove the button.\n        pickupLocation.querySelector('button[name=\"o_pickup_location_selector\"]')?.remove();\n    }\n\n    /**\n     * Remove the highlighting from the address card.\n     *\n     * @private\n     * @param card - The card element of the selected address.\n     * @return {void}\n     */\n    _tuneDownAddressCard(card) {\n        if (!card) return;\n        card.classList.remove('bg-400', 'border', 'border-primary');\n    }\n\n    /**\n     * Highlight the address card.\n     *\n     * @private\n     * @param card - The card element of the selected address.\n     * @return {void}\n     */\n    _highlightAddressCard(card) {\n        if (!card) return;\n        card.classList.add('bg-400', 'border', 'border-primary');\n    }\n\n    /**\n     * Disable the main button.\n     *\n     * @private\n     * @return {void}\n     */\n    _disableMainButton() {\n        this.mainButton?.classList.add('disabled');\n    }\n\n    /**\n     * Enable the main button if all conditions are satisfied.\n     *\n     * @private\n     * @return {void}\n     */\n    _enableMainButton() {\n        if (this._canEnableMainButton()) {\n            this.mainButton?.classList.remove('disabled');\n        }\n    }\n\n    /**\n     * Return whether a delivery method and a billing address are selected.\n     *\n     * @private\n     * @return {boolean}\n     */\n    _canEnableMainButton(){\n        return this._isDeliveryMethodReady() && this._isBillingAddressSelected();\n    }\n\n    /**\n     * Hide the pickup location.\n     *\n     * @private\n     * @return {void}\n     */\n    _hidePickupLocation() {\n        const pickupLocations = document.querySelectorAll(\n            '[name=\"o_pickup_location\"]:not(.d-none)'\n        );\n        pickupLocations.forEach(pickupLocation =>\n            pickupLocation.classList.add('d-none') // Hide the whole div.\n        );\n    }\n\n    /**\n     * Set the delivery method on the order and update the price badge and cart summary.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {void}\n     */\n    async _updateDeliveryMethod(radio) {\n        this._showLoadingBadge(radio);\n        const result = await this.waitFor(this._setDeliveryMethod(radio.dataset.dmId));\n        this._updateAmountBadge(radio, result);\n        this._updateCartSummaries(result);\n    }\n\n    /**\n     * Display a loading spinner on the delivery price badge.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {void}\n     */\n    _showLoadingBadge(radio) {\n        const deliveryPriceBadge = this._getDeliveryPriceBadge(radio);\n        this._clearElement(deliveryPriceBadge);\n        deliveryPriceBadge.appendChild(this._createLoadingElement());\n    }\n\n    /**\n     * Update the delivery price badge with the delivery rate.\n     *\n     * If the rate is zero, the price badge displays \"Free\" instead.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @param {Object} rateData - The delivery rate data.\n     * @return {void}\n     */\n    _updateAmountBadge(radio, rateData) {\n        const deliveryPriceBadge = this._getDeliveryPriceBadge(radio);\n        if (rateData.success) {\n            if (rateData.compute_price_after_delivery) {\n                // Inform the customer that the price will be computed after delivery.\n                deliveryPriceBadge.textContent = _t(\"Computed after delivery\");\n            } else if (rateData.is_free_delivery) {\n                // If it's a free delivery (`free_over` field), show 'Free', not '$ 0'.\n                deliveryPriceBadge.textContent = _t(\"Free\");\n            } else {\n                deliveryPriceBadge.innerHTML = rateData.amount_delivery;\n            }\n            this._toggleDeliveryMethodRadio(radio);\n        } else {\n            deliveryPriceBadge.textContent = rateData.error_message;\n            this._toggleDeliveryMethodRadio(radio, true);\n        }\n    }\n\n    /**\n     * Update the order summary table with the delivery rate of the selected delivery method.\n     *\n     * @private\n     * @param {Object} result - The order summary values.\n     * @param {Object} targetEl - Specific cart summary to update.\n     * @return {void}\n     */\n    _updateCartSummary(result, targetEl) {\n        const amountDelivery = targetEl.querySelector(\n            'tr[name=\"o_order_delivery\"] .monetary_field'\n        );\n        const amountUntaxed = targetEl.querySelector(\n            'tr[name=\"o_order_total_untaxed\"] .monetary_field'\n        );\n        const amountTax = targetEl.querySelector('tr[name=\"o_order_total_taxes\"] .monetary_field');\n        const amountTotal = targetEl.parentElement.querySelectorAll(\n            'tr[name=\"o_order_total\"] .monetary_field, #amount_total_summary.monetary_field'\n        );\n\n        // When no dm is set and a price span is hidden, hide the message and show the price span.\n        if (amountDelivery.classList.contains('d-none')) {\n            amountDelivery.querySelector('span[name=\"o_message_no_dm_set\"]').classList.add('d-none');\n            amountDelivery.classList.remove('d-none');\n        }\n\n        amountDelivery.innerHTML = result.amount_delivery;\n        amountUntaxed.innerHTML = result.amount_untaxed;\n        amountTax.innerHTML = result.amount_tax;\n        amountTotal.forEach(total => total.innerHTML = result.amount_total);\n    }\n\n    /**\n     * Update the order summary table with the delivery rate of the selected delivery method.\n     *\n     * @private\n     * @param {Object} result - The order summary values.\n     * @return {void}\n     */\n    _updateCartSummaries(result) {\n        const parentElements = document.querySelectorAll(\n            '#o_cart_summary_offcanvas, div.o_total_card'\n        );\n        parentElements.forEach(el => this._updateCartSummary(result, el));\n    }\n\n    /**\n     * Enable or disable radio selection for a delivery method.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @param {Boolean} disable - Whether the radio should be disabled.\n     */\n    _toggleDeliveryMethodRadio(radio, disable=false) {\n        const deliveryPriceBadge = this._getDeliveryPriceBadge(radio);\n        radio.disabled = disable;\n        if (disable) {\n            deliveryPriceBadge.classList.add('text-muted');\n        }\n        else {\n            deliveryPriceBadge.classList.remove('text-muted');\n        }\n    }\n\n    /**\n     * Remove all children of the provided element from the DOM.\n     *\n     * @private\n     * @param {Element} el - The element to clear.\n     * @return {void}\n     */\n    _clearElement(el) {\n        while (el.firstChild) {\n            el.removeChild(el.lastChild);\n        }\n    }\n\n    // #=== ADDRESS FLOW ===#\n\n    /**\n     * Select the billing address matching the currently selected delivery address.\n     *\n     * @private\n     * @param selectedPartnerId - The partner id of the selected delivery address.\n     * @return {void}\n     */\n    async _selectMatchingBillingAddress(selectedPartnerId) {\n        const previousAddress = this._getSelectedAddress('billing');\n        this._tuneDownAddressCard(previousAddress);\n        await this.waitFor(this.updateAddress('billing', selectedPartnerId));\n        const billingAddress = this.el.querySelector(\n            `.card[data-partner-id=\"${selectedPartnerId}\"][data-address-type=\"billing\"]`\n        );\n        this._highlightAddressCard(billingAddress);\n    }\n\n    /**\n     * Set the billing or delivery address on the order.\n     *\n     * @param addressType - The type of the address to set: 'delivery' or 'billing'.\n     * @param partnerId - The partner id of the address to set.\n     * @return {void}\n     */\n    async updateAddress(addressType, partnerId) {\n        await rpc('/shop/update_address', {address_type: addressType, partner_id: partnerId});\n    }\n\n    // #=== DELIVERY FLOW ===#\n\n    /**\n     * Change the delivery method to the one whose radio is selected and fetch all delivery rates.\n     *\n     * @private\n     * @return {void}\n     */\n    async _prepareDeliveryMethods() {\n        // Load the radios from the DOM here to update them if the template is re-rendered.\n        this.dmRadios = Array.from(document.querySelectorAll('input[name=\"o_delivery_radio\"]'));\n        if (this.dmRadios.length > 0) {\n            const checkedRadio = document.querySelector('input[name=\"o_delivery_radio\"]:checked');\n            this._disableMainButton();\n            if (checkedRadio) {\n                await this.waitFor(this._updateDeliveryMethod(checkedRadio));\n                this._enableMainButton();\n                await this._showPickupLocation(checkedRadio);\n            }\n        }\n        // Asynchronously fetch delivery rates to mitigate delays from third-party APIs\n        await Promise.all(this.dmRadios.filter(radio => !radio.checked).map(async radio => {\n            this._showLoadingBadge((radio));\n            const rateData = await this.waitFor(this._getDeliveryRate(radio));\n            this._updateAmountBadge(radio, rateData);\n        }));\n    }\n\n    /**\n     * Check if the delivery method is selected and if the pickup point is selected if needed.\n     *\n     * @private\n     * @return {boolean} Whether the delivery method is ready.\n     */\n    _isDeliveryMethodReady() {\n        if (this.dmRadios.length === 0) { // No delivery method is available.\n            return true; // Ignore the check.\n        }\n        const checkedRadio = document.querySelector('input[name=\"o_delivery_radio\"]:checked');\n        return checkedRadio\n            && !checkedRadio.disabled\n            && !this._isPickupLocationMissing(checkedRadio);\n    }\n\n    /**\n     * Get the delivery rate of the delivery method linked to the provided radio.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {Object} The delivery rate data.\n     */\n    async _getDeliveryRate(radio) {\n        return await rpc('/shop/get_delivery_rate', {'dm_id': radio.dataset.dmId});\n    }\n\n    /**\n     * Set the delivery method on the order and return the result values.\n     *\n     * @private\n     * @param {Integer} dmId - The id of selected delivery method.\n     * @return {Object} The result values.\n     */\n    async _setDeliveryMethod(dmId) {\n        return await rpc('/shop/set_delivery_method', {'dm_id': dmId});\n    }\n\n    /**\n     * Show the pickup location information or the button to open the location selector.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {void}\n     */\n    async _showPickupLocation(radio) {\n        if (!radio.dataset.isPickupLocationRequired || radio.disabled) {\n            return;  // Fetching the delivery rate failed.\n        }\n        const deliveryMethodContainer = this._getDeliveryMethodContainer(radio);\n        const pickupLocation = deliveryMethodContainer.querySelector('[name=\"o_pickup_location\"]');\n\n        const editPickupLocationButton = pickupLocation.querySelector(\n            'span[name=\"o_pickup_location_selector\"]'\n        );\n        if (editPickupLocationButton.dataset.pickupLocationData) {\n            await this.waitFor(\n                this._setPickupLocation(editPickupLocationButton.dataset.pickupLocationData)\n            );\n        }\n\n        pickupLocation.classList.remove('d-none'); // Show the whole div.\n    }\n\n    /**\n     * Set the pickup location on the order.\n     *\n     * @private\n     * @param {String} pickupLocationData - The pickup location's data to set.\n     * @return {void}\n     */\n    async _setPickupLocation(pickupLocationData) {\n        await rpc('/website_sale/set_pickup_location', {pickup_location_data: pickupLocationData});\n    }\n\n    // #=== GETTERS & SETTERS ===#\n\n    /** Determine and return the selected address who card has the class rowAddrClass.\n     *\n     * @private\n     * @param addressType - The type of the address: 'billing' or 'delivery'.\n     * @return {Element}\n     */\n    _getSelectedAddress(addressType) {\n        return this.el.querySelector(`.card.bg-400[data-address-type=\"${addressType}\"]`);\n    }\n\n    /**\n     * Return whether the \"use delivery as billing\" toggle is checked or a billing address is\n     * selected.\n     *\n     * @private\n     * @return {boolean} - Whether a billing address is selected.\n     */\n    _isBillingAddressSelected() {\n        const billingAddressSelected = Boolean(\n            this.el.querySelector('.card.bg-400[data-address-type=\"billing\"]')\n        );\n        return billingAddressSelected || this.useDeliveryAsBillingToggle?.checked;\n    }\n\n    /**\n     * Create and return an element representing a loading spinner.\n     *\n     * @private\n     * @return {Element} The created element.\n     */\n    _createLoadingElement() {\n        const loadingElement = document.createElement('i');\n        loadingElement.classList.add('fa', 'fa-circle-o-notch', 'fa-spin', 'center');\n        return loadingElement;\n    }\n\n    /**\n     * Return the delivery price badge element of the delivery method linked to the provided radio.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {Element} The delivery price badge element of the linked delivery method.\n     */\n    _getDeliveryPriceBadge(radio) {\n        const deliveryMethodContainer = this._getDeliveryMethodContainer(radio);\n        return deliveryMethodContainer.querySelector('.o_wsale_delivery_price_badge');\n    }\n\n    /**\n     * Return the container element of the delivery method linked to the provided element.\n     *\n     * @private\n     * @param {Element} el - The element linked to the delivery method.\n     * @return {Element} The container element of the linked delivery method.\n     */\n    _getDeliveryMethodContainer(el) {\n        return el.closest('[name=\"o_delivery_method\"]');\n    }\n\n    /**\n     * Return whether a pickup location is required but not selected.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {boolean} Whether a required pickup location is missing.\n     */\n    _isPickupLocationMissing(radio) {\n        const deliveryMethodContainer = this._getDeliveryMethodContainer(radio);\n        if (!this._isPickupLocationRequired(radio)) return false;\n        return !deliveryMethodContainer.querySelector(\n            'span[name=\"o_pickup_location_selector\"]'\n        ).dataset.locationId;\n    }\n\n    /**\n     * Return whether a pickup is required for the delivery method linked to the provided radio.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the delivery method.\n     * @return {bool} Whether a pickup is needed.\n     */\n    _isPickupLocationRequired(radio) {\n        return Boolean(radio.dataset.isPickupLocationRequired);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.checkout', Checkout);\n", "import { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\nimport { Form } from '@website/snippets/s_website_form/form';\n\npatch(Form.prototype, {\n    setup() {\n        super.setup();\n        this.dynamicSelectors = {\n            ...this.dynamicSelectors,\n            _submitbuttons: () => document.querySelectorAll('[name=\"website_sale_main_button\"]'),\n        };\n        patchDynamicContent(this.dynamicContent, {\n            _submitbuttons: { 't-on-click.prevent.stop': this.locked(this.send.bind(this), true) },\n        });\n    },\n});\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class OffCanvas extends Interaction {\n    static selector = '#o_wsale_offcanvas';\n    dynamicContent = {\n        _root: {\n            't-on-show.bs.offcanvas': this.toggleFilters,\n            't-on-hidden.bs.offcanvas': this.toggleFilters,\n        },\n    };\n\n    /**\n     * Unfold active filters, fold inactive ones\n     *\n     * @param {Event} ev\n     */\n    toggleFilters(ev) {\n        for (const btn of this.el.querySelectorAll('button[data-status]')) {\n            if (\n                btn.classList.contains('collapsed') && btn.dataset.status === 'active'\n                || !btn.classList.contains('collapsed') && btn.dataset.status === 'inactive'\n            ) {\n                btn.click();\n            }\n        }\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.off_canvas', OffCanvas);\n", "import { patch } from '@web/core/utils/patch';\nimport { PaymentButton } from '@payment/interactions/payment_button';\n\npatch(PaymentButton.prototype, {\n\n    /**\n     * Verify that the payment button is ready to be enabled.\n     *\n     * The conditions are that:\n     * - a delivery carrier is selected and ready (the price is computed) if deliveries are enabled;\n     * - the \"Terms and Conditions\" checkbox is ticked if it is present.\n     *\n     * @override method from @payment/interactions/payment_button\n     * @return {boolean}\n     */\n    _canSubmit() {\n        return super._canSubmit() && this._isTcCheckboxReady();\n    },\n\n    /**\n     * Check if the \"Terms and Conditions\" checkbox is ticked, if present.\n     *\n     * @private\n     * @return {boolean}\n     */\n    _isTcCheckboxReady() {\n        // Find the one T&C checkbox that is not hidden, either the desktop or the mobile one.\n        const checkboxes = document.querySelectorAll('#website_sale_tc_checkbox');\n        const visibleCheckbox = Array.from(checkboxes).find(el => el.offsetParent !== null);\n\n        if (!visibleCheckbox) { // The checkbox is not present.\n            return true; // Ignore the check.\n        }\n\n        return visibleCheckbox.checked;\n    },\n\n});\n", "import { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n\n     /**\n      * Create an event listener for the payment submit buttons located outside the payment form.\n      *\n      * Buttons that are inside the payment form are ignored as they are already handled by the\n      * payment form.\n      *\n      * @override\n     */\n     setup() {\n         super.setup();\n         const submitButtons = document.querySelectorAll('button[name=\"o_payment_submit_button\"]');\n         submitButtons.forEach(submitButton => {\n             if (!this.el.contains(submitButton)) {  // The button is outside the payment form.\n                 submitButton.addEventListener('click', ev => this.submitForm(ev));\n             }\n         });\n     }\n\n});\n", "import { patch } from \"@web/core/utils/patch\";\nimport { Popup } from \"@website/interactions/popup/popup\";\n\npatch(Popup.prototype, {\n    /**\n     * @override\n     */\n    canBtnPrimaryClosePopup(primaryBtnEl) {\n        return (\n            super.canBtnPrimaryClosePopup(...arguments)\n            && !primaryBtnEl.classList.contains(\"js_add_cart\")\n        );\n    },\n});\n", "import { Interaction } from '@web/public/interaction';\nimport { redirect } from '@web/core/utils/urls';\nimport { registry } from '@web/core/registry';\n\nexport class PriceRange extends Interaction {\n    static selector = '#o_wsale_price_range_option';\n    dynamicContent = {\n        'input[type=\"range\"]': { 't-on-newRangeValue': this.onPriceRangeSelected },\n    };\n\n    /**\n     * @param {Event} ev\n     */\n    onPriceRangeSelected(ev) {\n        const range = ev.currentTarget;\n        const url = new URL(range.dataset.url, window.location.origin);\n        const searchParams = url.searchParams;\n        if (parseFloat(range.min) !== range.valueLow) {\n            searchParams.set(\"min_price\", range.valueLow);\n        }\n        if (parseFloat(range.max) !== range.valueHigh) {\n            searchParams.set(\"max_price\", range.valueHigh);\n        }\n        const product_list_div = document.querySelector('.o_wsale_products_grid_table_wrapper');\n        if (product_list_div) {\n            product_list_div.classList.add('opacity-50');\n        }\n        redirect(`${url.pathname}?${searchParams.toString()}`);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.price_range', PriceRange);\n", "/** @odoo-module **/\n\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\n/**\n * Interaction that sets the height of images as a CSS custom property\n * on the product grid element. Used for responsive product grid layouts on mobile devices.\n */\nexport class ProductGridLayout extends Interaction {\n    static selector = \"#o-grid-product\";\n\n    dynamicContent = {\n        _window: {\n            \"t-on-resize\": this.debounced(this.onResize, 100),\n        },\n        _root: {\n            \"t-att-class\": () => ({\n                \"o_grid_product_ready\": this.isGridReady,\n            }),\n            \"t-att-style\": () => ({\n                \"--o-wsale-js-grid-product-height\": this.gridHeight || \"auto\",\n            }),\n        },\n    };\n\n    setup() {\n        this.gridHeight = null;\n        this.maxHeight = 0;\n        this.isGridReady = false;\n        this.loadedImages = new Set();\n\n        this.imagesEls = this.el.querySelectorAll('.product_detail_img');\n        this.isAutoRatioMode = this.el.classList.contains('o_grid_uses_ratio_auto') &&\n                               this.el.classList.contains('o_grid_uses_ratio_mobile_auto');\n    }\n\n    start() {\n        if (this.imagesEls.length === 0) {\n            return;\n        }\n\n        if (this.imagesEls.length === 1 || !this.isAutoRatioMode) {\n            this.handleStandardMode();\n        } else {\n            // Multiple images in auto ratio mode: use tallest\n            this.handleAutoRatioMode();\n        }\n\n        this.updateContent();\n    }\n\n    /**\n     * Handle standard mode - use first image height\n     */\n    handleStandardMode() {\n        const firstImage = this.imagesEls[0];\n\n        if (firstImage.complete && firstImage.naturalHeight !== 0) {\n            this.calculateImageHeight();\n        } else {\n            this.addListener(firstImage, 'load', this.calculateImageHeight);\n        }\n    }\n\n    /**\n     * Calculate and store the image height (standard mode)\n     */\n    calculateImageHeight() {\n        const firstImage = this.imagesEls[0];\n        if (!firstImage) return;\n\n        const height = firstImage.offsetHeight;\n        this.isGridReady = Boolean(height);\n        this.gridHeight = height ? `${height}px` : null;\n    }\n\n    /**\n     * Handle auto ratio mode - wait for all images and use tallest\n     */\n    handleAutoRatioMode() {\n        // Set 5-second timeout\n        const timeoutId = this.waitForTimeout(() => {\n            this.finalizeAutoRatioCalculation();\n        }, 5000);\n\n        this.imagesEls.forEach(imgEl => {\n            if (imgEl.complete && imgEl.naturalHeight !== 0) {\n                this.processLoadedImage(imgEl);\n            } else {\n                this.addListener(imgEl, 'load', () => {\n                    this.processLoadedImage(imgEl);\n\n                    // If all images are loaded, finalize early\n                    if (this.loadedImages.size === this.imagesEls.length) {\n                        clearTimeout(timeoutId);\n                        this.finalizeAutoRatioCalculation();\n                    }\n                });\n            }\n        });\n\n        // If all images were already loaded, finalize immediately\n        if (this.loadedImages.size === this.imagesEls.length) {\n            clearTimeout(timeoutId);\n            this.finalizeAutoRatioCalculation();\n        }\n    }\n\n    /**\n     * Process a loaded image and track its height\n     */\n    processLoadedImage(imgEl) {\n        this.loadedImages.add(imgEl);\n        const height = imgEl.offsetHeight;\n        if (height > this.maxHeight) {\n            this.maxHeight = height;\n        }\n    }\n\n    /**\n     * Finalize calculation for auto ratio mode\n     */\n    finalizeAutoRatioCalculation() {\n        this.isGridReady = true;\n        this.gridHeight = this.maxHeight ? `${this.maxHeight}px` : null;\n    }\n\n    /**\n     * On page resize, recalculate the image height (mobile only)\n     */\n    onResize() {\n        if (!this.env.isSmall) {\n            return;\n        }\n\n        if (this.isAutoRatioMode) {\n            // Recalculate max height from all loaded images\n            this.maxHeight = 0;\n            this.loadedImages.forEach(imgEl => {\n                const height = imgEl.offsetHeight;\n                if (height > this.maxHeight) {\n                    this.maxHeight = height;\n                }\n            });\n            this.gridHeight = this.maxHeight ? `${this.maxHeight}px` : null;\n        } else {\n            this.calculateImageHeight();\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.website_sale_product_grid_layout\", ProductGridLayout);\n\nregistry\n    .category(\"public.interactions.edit\")\n    .add(\"website.website_sale_product_grid_layout\", { Interaction: ProductGridLayout });\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class ProductVariantPreview extends Interaction {\n    static selector = \"#o_wsale_products_grid\";\n\n    dynamicContent = {\n        _window: {\n            \"t-on-resize\": this.debounced(this.updateVariantPreview, 250),\n        },\n    };\n\n    setup() {\n        // Class `gap-1` on parent adds 4px margin for each ptav.\n        this.margin = 4;\n        this.updateVariantPreview();\n    }\n\n    /**\n     * Hide all attribute values from view to be able to recompute correctly how many elements are\n     * to be shown.\n     *\n     * @private\n     *\n     * @returns {void}\n     */\n    _resetDisplay(attributePreviewer) {\n        for (const child of attributePreviewer.children) {\n            child.classList.add('d-none');\n        }\n    }\n\n    /**\n     * Update the count of hidden PTAVs with the correct number and make it visible.\n     *\n     * @private\n     * @param {Element} currentPTAV\n     * @param {Number} remainingSpace\n     *\n     * @returns {void}\n     */\n    _showHiddenPTAVsElement(\n        attributePreviewerValues, currentPTAV, remainingSpace, displayedPTAVCount\n    ) {\n        const {\n            ptavCount,\n            offsetWidthPTAVS,\n            hiddenCountSpan,\n            hiddenCountSpanWidth,\n        } = attributePreviewerValues;\n        while (currentPTAV && hiddenCountSpanWidth >= remainingSpace) {\n            currentPTAV.classList.add(\"d-none\");\n            displayedPTAVCount--;\n            remainingSpace += offsetWidthPTAVS.get(currentPTAV);\n            currentPTAV = currentPTAV.previousElementSibling;\n        }\n        const hiddenPTAVCount = ptavCount - displayedPTAVCount;\n        hiddenCountSpan.firstElementChild.textContent = `+${hiddenPTAVCount}`;\n        hiddenCountSpan.classList.remove(\"d-none\");\n    }\n\n    /**\n     * For each ptav check if there is enough space to add on the parent element and update the\n     * hidden PTAVs count accordingly, with the truncated elements from the backend.\n     *\n     * @private\n     *\n     * @returns {void}\n     */\n    _updateVariantPreview(attributePreviewer, attributePreviewerValues) {\n        const { containerWidth, ptavs, ptavCount, offsetWidthPTAVS } = attributePreviewerValues;\n        this._resetDisplay(attributePreviewer);\n        let usedWidth = 0;\n        let displayedPTAVCount = 0;\n        for (const ptav of ptavs) {\n            ptav.classList.remove('d-none');\n            usedWidth += offsetWidthPTAVS.get(ptav) + this.margin;\n            displayedPTAVCount++;\n            const remainingSpace = containerWidth - usedWidth;\n            const isLastPTAV = ptav === ptavs[ptavs.length - 1];\n            const hasHiddenPtavs = isLastPTAV && ptavCount > displayedPTAVCount;\n            if (usedWidth >= containerWidth || hasHiddenPtavs) {\n                this._showHiddenPTAVsElement(\n                    attributePreviewerValues, ptav, remainingSpace, displayedPTAVCount,\n                );\n                break;\n            }\n        }\n    }\n\n    /**\n     * Triggered on the parent element of the '.o_wsale_attribute_previewer' elements to run the\n     * interaction once instead of multiple times depending on how many elements exist on the page.\n     *\n     * Schedules and batches updates for all active '.o_wsale_attribute_previewer' elements\n     * to refresh their variant previews efficiently.\n     *\n     * Uses `requestAnimationFrame` to ensure that updates occur in sync with the browser\u2019s\n     * rendering cycle, preventing redundant or frequent recalculations (trigger by offsetWidth).\n     */\n    updateVariantPreview() {\n        const attributePreviewers = this.el.querySelectorAll(\".o_wsale_attribute_previewer\");\n        const updateAllVariantPreview = this.protectSyncAfterAsync(() => {\n            const attributePreviewerValues = new Map();\n\n            // Initiate the values needed for each attribute previewer.\n            for (const attributePreviewer of attributePreviewers) {\n                this._resetDisplay(attributePreviewer);\n                const ptavs = attributePreviewer.querySelectorAll(\".o_product_variant_preview\");\n                // Set the hiddenCountSpan to the maximum number of ptavs there is to assume\n                // the worst case space it needs.\n                const hiddenCountSpan = attributePreviewer.querySelector(\n                    \"span[name='hidden_ptavs_count']\");\n                const ptavCount = ptavs.length + Number(\n                    attributePreviewer.dataset.hiddenPtavCount ?? 0);\n                hiddenCountSpan.firstElementChild.textContent = `+${ptavCount}`;\n                hiddenCountSpan.classList.remove(\"d-none\");\n                attributePreviewerValues.set(\n                    attributePreviewer,\n                    {\n                        containerWidth: attributePreviewer.offsetWidth,\n                        ptavs,\n                        hiddenCountSpan,\n                        ptavCount,\n                        offsetWidthPTAVS: new Map(),\n                        hiddenCountSpanWidth: 0,\n                    },\n                );\n            }\n\n            // Display all hidden elements to get the correct width.\n            for (const attributePreviewer of attributePreviewers) {\n                const currentValues = attributePreviewerValues.get(attributePreviewer);\n                for (const ptav of currentValues.ptavs) {\n                    ptav.classList.remove(\"d-none\");\n                }\n            }\n\n            // A recalculation of the styles is triggered every time offsetWidth is called.\n            // Get all offsetWidths in one step to avoid recalculation for each element separately.\n            for (const attributePreviewer of attributePreviewers) {\n                const currentValues = attributePreviewerValues.get(attributePreviewer);\n                for (const ptav of currentValues.ptavs) {\n                    currentValues.offsetWidthPTAVS.set(ptav, ptav.offsetWidth);\n                }\n                currentValues.hiddenCountSpanWidth = (\n                    currentValues.hiddenCountSpan.offsetWidth + this.margin * 2\n                );\n            }\n\n            for (const attributePreviewer of attributePreviewers) {\n                this._updateVariantPreview(\n                    attributePreviewer, attributePreviewerValues.get(attributePreviewer)\n                );\n            }\n        });\n        requestAnimationFrame(updateAllVariantPreview);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.product_variant_preview', ProductVariantPreview);\n\nregistry\n    .category(\"public.interactions.edit\")\n    .add(\"website.product_variant_preview\", { Interaction: ProductVariantPreview });\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class ProductVariantPreviewImageHover extends Interaction {\n    static selector = '.oe_product_cart.o_has_variations';\n    dynamicContent = {\n        '.o_product_variant_preview': {\n            't-on-mouseenter': this._mouseEnter,\n            't-on-mouseleave': this._mouseLeave,\n            't-on-click': this._onClick,\n        },\n    };\n\n    setup() {\n        this.productImg = this.el.querySelector('.oe_product_image_img_wrapper_primary img');\n        this.originalImgSrc = this.productImg.getAttribute('src');\n    }\n\n    /**\n     * Display the variant image on hover.\n     *\n     * @private\n     * @param {Event} ev\n     *\n     * @returns {void}\n     */\n    _mouseEnter(ev) {\n        if (!this.env.isSmall) {\n            const variantImageSrc = ev.target.dataset.variantImage;\n            if (!variantImageSrc) {\n                return;\n            }\n            this._setImgSrc(variantImageSrc);\n        }\n    }\n\n    /**\n     * Reset the product image when mouse no longer hovers on the ptav.\n     *\n     * @private\n     *\n     * @returns {void}\n     */\n    _mouseLeave() {\n        if (!this.env.isSmall) {\n            this._setImgSrc(this.originalImgSrc);\n        }\n    }\n\n    /**\n     * Set the image source of the product to the given image source\n     *\n     * @param {string} imageSrc\n     */\n    _setImgSrc(imageSrc) {\n        this.productImg.src = imageSrc;\n    }\n\n    /**\n     * On mobile, when ptav is clicked simulate on hover behavior and change product image\n     * to variant image.\n     * The href of product card is changed to match that of the selected variant.\n     *\n     * @param {Event} ev\n     * @returns\n     */\n    _onClick(ev) {\n        if (this.env.isSmall) {\n            ev.preventDefault();\n            const targetElement = ev.target.closest('.o_product_variant_preview');\n            const productCard = ev.target.closest('.oe_product_cart');\n            productCard.querySelector('.oe_product_image_link').href = targetElement.href;\n            const variantImageSrc = targetElement.dataset.variantImage;\n            if (!variantImageSrc) {\n                return;\n            }\n            this._setImgSrc(variantImageSrc);\n        }\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.product_variant_preview_image_hover', ProductVariantPreviewImageHover);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class ProductAccordion extends Interaction {\n    static selector = '#product_accordion';\n\n    setup() {\n        this._updateAccordionActiveItem();\n    }\n\n    /**\n     * Open the first accordion item by default.\n     */\n    _updateAccordionActiveItem() {\n        const firstAccordionItemEl = this.el.querySelector('.accordion-item');\n        if (!firstAccordionItemEl) return;\n\n        const firstAccordionItemButtonEl = firstAccordionItemEl.querySelector('.accordion-button');\n        firstAccordionItemButtonEl.classList.remove('collapsed');\n        firstAccordionItemButtonEl.setAttribute('aria-expanded', 'true');\n        firstAccordionItemEl.querySelector('.accordion-collapse').classList.add('show');\n        this.el.classList.remove('o_accordion_not_initialized');\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.product_accordion', ProductAccordion);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class ProductStickyCol extends Interaction {\n    static selector = '.o_wsale_product_sticky_col';\n    dynamicContent = {\n        _root: {\n            't-att-style': () => ({\n                'opacity': '1',\n                'top': `${this.position || 16}px`,\n            }),\n        }\n    };\n\n    setup() {\n        this.position = 16;\n    }\n\n    start() {\n        this._adaptToHeaderChange();\n        this.registerCleanup(\n            this.services.website_menus.registerCallback(this._adaptToHeaderChange.bind(this))\n        );\n    }\n\n    _adaptToHeaderChange() {\n        let position = 16; // Add 1rem equivalent in px to provide a visual gap by default\n\n        for (const el of document.querySelectorAll('.o_top_fixed_element')) {\n            position += el.offsetHeight;\n        }\n\n        if (this.position !== position) {\n            this.position = position;\n            this.updateContent();\n        }\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.product_sticky_col', ProductStickyCol);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class ProductTileSecondaryImage extends Interaction {\n    static selector = '.oe_product_image_link_has_secondary';\n    dynamicContent = {\n        _root: {\n            \"t-att-class\": () => ({ \"o_product_tile_scrolled\": this.isSecondImgInView }),\n            \"t-on-scroll\": (ev) => this.onScroll(ev),\n        }\n    };\n\n    setup() {\n        this.isSecondImgInView = false;\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n    onScroll(ev) {\n        this.isSecondImgInView = ev.target.scrollLeft > ev.target.scrollWidth * 0.25;\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.website_sale_product_tile_secondary_image\", ProductTileSecondaryImage);\n", "import { ProductCombo } from '@sale/js/models/product_combo';\nimport { serializeComboItem } from '@sale/js/sale_utils';\nimport { serializeDateTime } from '@web/core/l10n/dates';\nimport { rpc } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { Interaction } from '@web/public/interaction';\nimport wSaleUtils from '@website_sale/js/website_sale_utils';\n\nexport class QuickReorder extends Interaction {\n\n    static selector = '#quick_reorder_sidebar';\n    dynamicContent = {\n        '.o_wsale_quick_reorder_qty_input': {\n            't-on-input': this.updateQuantityAndPrice,\n            't-on-keydown': this.triggerReorderOnEnter,\n        },\n        '.o_wsale_quick_reorder_product_button': { 't-on-click': this.reorderProduct },\n    };\n\n    /**\n     * Update the total price and enable/disable the add button based on the quantity input.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async updateQuantityAndPrice(ev) {\n        const qtyInput = ev.currentTarget;\n        const priceUnit = parseFloat(qtyInput.dataset.priceUnit);\n        const digits = parseInt(qtyInput.dataset.currencyDigits, 10);\n        const qty = parseInt(qtyInput.value, 10) || 0;\n        this._updateAddButton(qtyInput, qty);\n        if (qty > 0) {\n            this._updateTotalPrice(qtyInput, qty, priceUnit, digits);\n        }\n    }\n\n    /**\n     * Update the add button state based on quantity.\n     *\n     * @private\n     * @param {Element} qtyInput - The quantity input element.\n     * @param {number} qty - The quantity value.\n     * @return {void}\n     */\n    _updateAddButton(qtyInput, qty) {\n        const addButton = qtyInput.closest('.o_wsale_quick_reorder_line').querySelector(\n            '.o_wsale_quick_reorder_product_button'\n        );\n        const isDisabled = qty <= 0;\n        addButton.classList.toggle('disabled', isDisabled);\n        if (qty > 0) {\n            addButton.dataset.quantity = String(qty);\n        }\n    }\n\n    /**\n     * Update the total price display for the related line.\n     *\n     * @private\n     * @param {Element} qtyInput - The quantity input element.\n     * @param {number} qty - The quantity.\n     * @param {number} priceUnit - The unit price.\n     * @param {number} digits - The number of decimal digits for the currency.\n     * @return {void}\n     */\n    _updateTotalPrice(qtyInput, qty, priceUnit, digits) {\n        const priceEl = qtyInput.closest('.o_wsale_quick_reorder_line').querySelector(\n            '.o_wsale_quick_reorder_product_price .oe_currency_value'\n        );\n        if (priceEl) {\n            const totalPrice = (qty * priceUnit).toFixed(digits);\n            priceEl.textContent = totalPrice;\n        }\n    }\n\n    /**\n     * Trigger the reorder action when Enter key is pressed on quantity input.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async triggerReorderOnEnter(ev) {\n        if (ev.key !== 'Enter') return;\n\n        const addButton = ev.currentTarget.closest('.o_wsale_quick_reorder_line').querySelector(\n            '.o_wsale_quick_reorder_product_button'\n        );\n        if (addButton && !addButton.classList.contains('disabled')) {\n            addButton.click();\n        }\n    }\n\n    /**\n     * Reorder the product and update the page's content.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async reorderProduct(ev) {\n        // Extract product data from the button dataset.\n        const addButtonDataset = ev.currentTarget.dataset;\n        const productTemplateId = parseInt(addButtonDataset.productTemplateId, 10);\n        const productId = parseInt(addButtonDataset.productId, 10);\n        let quantity = parseInt(addButtonDataset.quantity);\n        const isCombo = addButtonDataset.productType === 'combo';\n        const selectedComboItems = JSON.parse(addButtonDataset.selectedComboItems || '[]');\n\n        // Capture the button index before DOM updates.\n        const allButtons = document.querySelectorAll('.o_wsale_quick_reorder_product_button');\n        const currentButtonIndex = Array.from(allButtons).indexOf(ev.currentTarget);\n\n        // Process combo products if applicable.\n        let linkedProducts = [];\n        if (isCombo) {\n            const { quantity: updatedQty, combos } = await rpc(\n                '/website_sale/combo_configurator/get_data',\n                {\n                    product_tmpl_id: productTemplateId,\n                    quantity: quantity,\n                    date: serializeDateTime(luxon.DateTime.now()),\n                    selected_combo_items: selectedComboItems,\n                }\n            );\n            quantity = updatedQty;\n            linkedProducts = combos\n                .map(combo => new ProductCombo(combo).selectedComboItem)\n                .filter(Boolean)\n                .map(comboItem => ({\n                    product_template_id: comboItem.product.product_tmpl_id,\n                    parent_product_template_id: productTemplateId,\n                    quantity: quantity,\n                    ...serializeComboItem(comboItem),\n                }));\n        }\n\n        const data = await this.waitFor(rpc('/shop/cart/quick_add', {\n            product_template_id: productTemplateId,\n            product_id: productId,\n            quantity: quantity,\n            ...(isCombo && { linked_products: linkedProducts }),\n        }));\n\n        // Add the product to the cart and update the DOM.\n        const cart = document.getElementById('shop_cart');\n        // `updateCartNavBar` regenerates the cart lines and `updateQuickReorderSidebar`\n        // regenerates the quick reorder products, so we need to stop and start interactions to\n        // make sure the regenerated reorder products and cart lines are properly handled.\n        this.services['public.interactions'].stopInteractions(cart);\n        wSaleUtils.updateCartNavBar(data);\n        wSaleUtils.updateQuickReorderSidebar(data);\n        this.services['public.interactions'].startInteractions(cart);\n\n        // Move the focus to the next quantity input.\n        this._focusNextQuantityInput(currentButtonIndex);\n    }\n\n    /**\n     * Moves the focus to the next quantity input.\n     *\n     * @param {HTMLElement} buttonIndex - The index of the reorder button that was clicked before\n     *                                    DOM updates.\n     * @return {void}\n     */\n    _focusNextQuantityInput(buttonIndex) {\n        const allQuantityInputs = document.querySelectorAll('.o_wsale_quick_reorder_qty_input');\n        const nextInput = allQuantityInputs[buttonIndex];\n        if (nextInput) {\n            nextInput.focus();\n            nextInput.select();\n        }\n    }\n\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.quick_reorder', QuickReorder);\n", "import { cookie } from '@web/core/browser/cookie';\nimport { rpc } from '@web/core/network/rpc';\nimport { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class RecentlyViewedProducts extends Interaction {\n    static selector = '.o_wsale_product_page';\n    dynamicContent = {\n        'input.product_id[name=\"product_id\"]': {\n            't-on-change.withTarget': this.debounced(this.onProductChange, 500),\n        },\n    };\n\n    /**\n     * Mark the product as viewed.\n     *\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onProductChange(ev, currentTargetEl) {\n        if (!parseInt(this.el.querySelector('#product_detail').dataset.viewTrack)) {\n            return; // Product not tracked.\n        }\n        const productId = parseInt(currentTargetEl.value);\n        const cookieName = 'seen_product_id_' + productId;\n        if (cookie.get(cookieName)) {\n            return; // Product already tracked in the last 30 min.\n        }\n        if (this.el.querySelector('.js_product.css_not_available')) {\n            return; // Product not available.\n        }\n        await this.waitFor(rpc('/shop/products/recently_viewed_update', {\n            product_id: productId,\n        }));\n        cookie.set(cookieName, productId, 30 * 60, 'optional');\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.recently_viewed_products', RecentlyViewedProducts);\n", "import { browser } from '@web/core/browser/browser';\nimport { rpc } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { redirect } from '@web/core/utils/urls';\nimport { Interaction } from '@web/public/interaction';\n\nexport class SaleOrderPortalReorder extends Interaction {\n    static selector = '#sale_order_sidebar_button';\n    dynamicContent = {\n        'button#reorder_sidebar_button': { 't-on-click': this.onReorder },\n    };\n\n    /**\n     * Handles the reorder functionality when the reorder button is clicked.\n     * Does the reorder by calling the `/my/orders/reorder` endpoint with the order ID and\n     * access token.\n     *\n     * @param {Event} ev - The event triggered when the reorder button is clicked.\n     */\n    async onReorder(ev) {\n        this.orderId = parseInt(ev.currentTarget.dataset.saleOrderId);\n        this.accessToken = new URLSearchParams(window.location.search).get('access_token');\n        if (!this.orderId) return;\n\n        await this._doReorder();\n    }\n\n    async _doReorder() {\n        try {\n            const values = await this.waitFor(rpc('/my/orders/reorder', {\n                order_id: this.orderId,\n                access_token: this.accessToken,\n            }));\n\n            // Sync cart quantity in session storage when adding reorder products from backend,\n            // since `website_sale_cart_quantity` updates only via the cart service.\n            browser.sessionStorage.setItem('website_sale_cart_quantity', values.cart_quantity);\n\n            this._trackProducts(values.tracking_info);\n            redirect('/shop/cart');\n        } catch (error) {\n            console.error(\"Error during reordering:\", error);\n        }\n    }\n\n    /**\n     * Track the products added to the cart.\n     *\n     * @private\n     * @param {Object[]} trackingInfo - A list of product tracking information.\n     *\n     * @returns {void}\n     */\n    _trackProducts(trackingInfo) {\n        document.querySelector('.oe_website_sale').dispatchEvent(\n            new CustomEvent('add_to_cart_event', {'detail': trackingInfo})\n        );\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.portal_reorder', SaleOrderPortalReorder);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class SearchModal extends Interaction {\n    static selector = '#o_wsale_search_modal';\n\n    start() {\n        this.el.addEventListener('shown.bs.modal', (ev) =>\n            ev.target.querySelector('.oe_search_box').focus()\n        );\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.search_modal', SearchModal);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class WebsiteSaleStickyObject extends Interaction {\n    static selector = \".o_wsale_sticky_object\";\n\n    dynamicContent = {\n        _root: {\n            \"t-att-style\": () => ({\n                \"top\": `${this.position || 16}px`,\n            }),\n        }\n    };\n\n    setup() {\n        this.position = 16;\n    }\n\n    start() {\n        this._adaptToHeaderChange();\n        this.registerCleanup(this.services.website_menus.registerCallback(this._adaptToHeaderChange.bind(this)));\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n\n    _adaptToHeaderChange() {\n        let position = 16; // Add 1rem equivalent in px to provide a visual gap by default\n\n        for (const el of this.el.ownerDocument.querySelectorAll(\".o_top_fixed_element\")) {\n            position += el.offsetHeight;\n        }\n\n        if (this.position !== position) {\n            this.position = position;\n            this.updateContent();\n        }\n    }\n}\nregistry\n    .category(\"public.interactions\")\n    .add(\"website.website_sale_product_sticky_col\", WebsiteSaleStickyObject);\n\nregistry\n    .category(\"public.interactions.edit\")\n    .add(\"website.website_sale_product_sticky_col\", { Interaction: WebsiteSaleStickyObject});\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class TermsAndConditionsCheckbox extends Interaction {\n    static selector = 'div[name=\"website_sale_terms_and_conditions_checkbox\"]';\n    dynamicContent = {\n        '#website_sale_tc_checkbox': { 't-on-change': this.onClickTcCheckbox },\n    };\n\n    setup() {\n        this.checkbox = this.el.querySelector('#website_sale_tc_checkbox');\n    }\n\n    /**\n     * Enable/disable the payment button when the \"Terms and Conditions\" checkbox is\n     * checked/unchecked.\n     *\n     * @return {void}\n     */\n    onClickTcCheckbox() {\n        this.env.bus.trigger(\n            this.checkbox.checked ? 'enablePaymentButton' : 'disablePaymentButton'\n        );\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.terms_and_conditions_checkbox', TermsAndConditionsCheckbox);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\n\nexport class Tracking extends Interaction {\n    static selector = '.oe_website_sale';\n    dynamicContent = {\n        'form a.a-submit': { 't-on-click': this.onAddProductToCart },\n        'a[href^=\"/shop/checkout\"]': { 't-on-click': this.onCheckoutStart },\n        'a[href^=\"/web/login?redirect\"][href*=\"/shop/checkout\"]': {\n            't-on-click': this.onCustomerSignin,\n        },\n        'a[href=\"/shop/payment\"]': { 't-on-click': this.onOrder },\n        'button[name=\"o_payment_submit_button\"]': { 't-on-click': this.onOrderPayment },\n        _root: {\n            't-on-view_item_event': (ev) => this.onViewItem(ev),\n            't-on-add_to_cart_event': (ev) => this.onAddToCart(ev),\n        },\n    };\n\n    setup() {\n        const confirmation = this.el.querySelector('div[name=\"order_confirmation\"]');\n        if (confirmation) {\n            this._vpv('/stats/ecom/order_confirmed/' + confirmation.dataset.orderId);\n            this._trackGa('event', 'purchase', confirmation.dataset.orderTrackingInfo);\n        }\n    }\n\n    /**\n     * @private\n     */\n    _trackGa() {\n        const websiteGA = window.gtag || (() => {});\n        websiteGA.apply(this, arguments);\n    }\n\n    /**\n     * Virtual page view\n     *\n     * @private\n     */\n    _vpv(page) {\n        this._trackGa('event', 'page_view', { 'page_path': page });\n    }\n\n    onViewItem(event) {\n        const productTrackingInfo = event.detail;\n        const trackingInfo = {\n            'currency': productTrackingInfo['currency'],\n            'value': productTrackingInfo['price'],\n            'items': [productTrackingInfo],\n        };\n        this._trackGa('event', 'view_item', trackingInfo);\n    }\n\n    onAddToCart(event) {\n        const productsTrackingInfo = event.detail;\n        const trackingInfo = {\n            'currency': productsTrackingInfo[0]['currency'],\n            'value': productsTrackingInfo.reduce(\n                (acc, val) => acc + val['price'] * val['quantity'], 0\n            ),\n            'items': productsTrackingInfo,\n        };\n        this._trackGa('event', 'add_to_cart', trackingInfo);\n    }\n\n    onAddProductToCart() {\n        const productId = this.el.querySelector('input[name=\"product_id\"]')?.getAttribute('value');\n        if (productId) {\n            this._vpv('/stats/ecom/product_add_to_cart/' + productId);\n        }\n    }\n\n    onCheckoutStart() {\n        this._vpv('/stats/ecom/customer_checkout');\n    }\n\n    onCustomerSignin() {\n        this._vpv('/stats/ecom/customer_signin');\n    }\n\n    onOrder() {\n        if (document.querySelector('header#top [href=\"/web/login\"]')) {\n            this._vpv('/stats/ecom/customer_signup');\n        }\n        this._vpv('/stats/ecom/order_checkout');\n    }\n\n    onOrderPayment() {\n        const paymentMethod = this.el.querySelector(\n            '#payment_method input[name=\"o_payment_radio\"]:checked'\n        )?.parentElement?.querySelector('.o_payment_option_label')?.textContent;\n        this._vpv('/stats/ecom/order_payment/' + paymentMethod);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.tracking', Tracking);\n", "import VariantMixin from '@website_sale/js/variant_mixin';\nimport { renderToFragment } from '@web/core/utils/render';\nimport { formatFloat } from '@web/core/utils/numbers';\nimport { setElementContent } from '@web/core/utils/html';\n\n\nimport { markup } from \"@odoo/owl\";\n\n/**\n * Addition to the variant_mixin._onChangeCombination\n *\n * This will prevent the user from selecting a quantity that is not available in the\n * stock for that product.\n *\n * It will also display various info/warning messages regarding the select product's stock.\n *\n * This behavior is only applied for the web shop (and not on the SO form)\n * and only for the main product.\n *\n * @param {MouseEvent} ev\n * @param {Element} parent\n * @param {Array} combination\n */\nVariantMixin._onChangeCombinationStock = async function (ev, parent, combination) {\n    const has_max_combo_quantity = 'max_combo_quantity' in combination\n    if (!combination.is_storable && !has_max_combo_quantity) {\n        return;\n    }\n\n    if (!parent.matches('.js_main_product') || !combination.product_id) {\n        // if we're not on product page or the product is dynamic\n        return;\n    }\n\n    const addQtyInput = parent.querySelector('input[name=\"add_qty\"]');\n    const qty = parseFloat(addQtyInput?.value) || 1;\n    const ctaWrapper = parent.querySelector('#o_wsale_cta_wrapper');\n    ctaWrapper.classList.replace('d-none', 'd-flex');\n    ctaWrapper.classList.remove('out_of_stock');\n\n    if (!combination.allow_out_of_stock_order) {\n        const unavailableQty = await this.waitFor(VariantMixin._getUnavailableQty(combination));\n        combination.free_qty -= unavailableQty;\n        if (combination.free_qty < 0) {\n            combination.free_qty = 0;\n        }\n        if (addQtyInput) {\n            addQtyInput.dataset.max = combination.free_qty || 1;\n            if (qty > combination.free_qty) {\n                addQtyInput.value = addQtyInput.dataset.max;\n            }\n        }\n        if (combination.free_qty < 1) {\n            ctaWrapper.classList.replace('d-flex', 'd-none');\n            ctaWrapper.classList.add('out_of_stock');\n        }\n    } else if (has_max_combo_quantity) {\n        if (addQtyInput) {\n            addQtyInput.dataset.max = combination.max_combo_quantity || 1;\n            if (qty > combination.max_combo_quantity) {\n                addQtyInput.value = addQtyInput.dataset.max;\n            }\n        }\n        if (combination.max_combo_quantity < 1) {\n            ctaWrapper.classList.replace('d-flex', 'd-none');\n            ctaWrapper.classList.add('out_of_stock');\n        }\n    }\n\n    // needed xml-side for formatting of remaining qty\n    combination.formatQuantity = (qty) => {\n        if (Number.isInteger(qty)) {\n            return qty;\n        } else {\n            const decimals = Math.max(\n                0,\n                Math.ceil(-Math.log10(combination.uom_rounding))\n            );\n            return formatFloat(qty, {digits: [false, decimals]});\n        }\n    }\n\n    document.querySelector('.oe_website_sale')\n        .querySelectorAll('.availability_message_' + combination.product_template)\n        .forEach(el => el.remove());\n    if (combination.out_of_stock_message) {\n        combination.out_of_stock_message = markup(combination.out_of_stock_message);\n        const outOfStockMessage = document.createElement('div');\n        setElementContent(outOfStockMessage, combination.out_of_stock_message);\n        combination.has_out_of_stock_message = !!outOfStockMessage.textContent.trim();\n    }\n    this.el.querySelector('div.availability_messages').append(renderToFragment(\n        'website_sale_stock.product_availability', combination\n    ));\n};\n\nVariantMixin._getUnavailableQty = async function (combination) {\n    return parseInt(combination.cart_qty);\n};\n\nexport default VariantMixin;\n", "import VariantMixin from '@website_sale_stock/js/variant_mixin';\nimport { renderToElement } from '@web/core/utils/render';\n\nconst oldChangeCombinationStock = VariantMixin._onChangeCombinationStock;\n/**\n * Displays additional info messages regarding the product's\n * stock and the wishlist.\n *\n * @override\n */\nVariantMixin._onChangeCombinationStock = function (ev, parent, combination) {\n    oldChangeCombinationStock.apply(this, arguments);\n    if (this.el.querySelector('.o_add_wishlist_dyn')) {\n        const messageEl = this.el.querySelector('div.availability_messages');\n        if (messageEl && !this.el.querySelector('#stock_wishlist_message')) {\n            messageEl.append(\n                renderToElement('website_sale_stock_wishlist.product_availability', combination) || ''\n            );\n        }\n    }\n};\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { hasTouch, isBrowserFirefox } from '@web/core/browser/feature_detection';\nimport { redirect, url } from '@web/core/utils/urls';\nimport { uniqueId } from '@web/core/utils/functions';\nimport { markup } from '@odoo/owl';\nimport wSaleUtils from '@website_sale/js/website_sale_utils';\nimport { ProductImageViewer } from '@website_sale/js/components/website_sale_image_viewer';\nimport VariantMixin from '@website_sale/js/variant_mixin';\n\nexport class WebsiteSale extends Interaction {\n    static selector = '.oe_website_sale';\n    dynamicContent = {\n        '.js_main_product input[name=\"add_qty\"]': { 't-on-change': this.onChangeAddQuantity },\n        'a.js_add_cart_json': { 't-on-click.prevent': this.onChangeQuantity },\n        'form.js_attributes input, form.js_attributes select': {\n            't-on-change.prevent': this.onChangeAttribute,\n        },\n        '.o_wsale_products_searchbar_form': { 't-on-submit': this.onSubmitSaleSearch },\n        '#add_to_cart, .o_we_buy_now, #products_grid .o_wsale_product_btn .a-submit': {\n            't-on-click.prevent': this.onClickAdd,\n        },\n        '.js_main_product [data-attribute-exclusions]': { 't-on-change': this.onChangeVariant },\n        '.o_product_page_reviews_link': { 't-on-click': this.onClickReviewsLink },\n        '.o_wsale_filmstrip_wrapper': {\n            't-on-mousedown': this.onMouseDown,\n            't-on-mouseleave': this.onMouseLeave,\n            't-on-mouseup': this.onMouseUp,\n            't-on-mousemove': this.onMouseMove,\n            't-on-click': this.onClickHandler,\n        },\n        'form[name=\"o_wsale_confirm_order\"]': {\n            't-on-submit': this.locked(this.onClickConfirmOrder),\n        },\n        '.o_wsale_attribute_search_bar': { 't-on-input': this.searchAttributeValues },\n        '.o_wsale_view_more_btn': { 't-on-click': this.onToggleViewMoreLabel },\n        '.css_attribute_color input': { 't-on-change': this.onChangeColorAttribute },\n        'label[name=\"o_wsale_attribute_image_selector\"] input': {\n            't-on-change': this.onChangeImageAttribute,\n        },\n        '.o_variant_pills': { 't-on-click': this.onChangePillsAttribute },\n    };\n\n    setup() {\n        this.isWebsite = true;\n        this.filmStripStartX = 0;\n        this.filmStripIsDown = false;\n        this.filmStripScrollLeft = 0;\n        this.filmStripMoved = false;\n        this.imageRatio = this.el.dataset.imageRatio;\n    }\n\n    start() {\n        this._applySearch();\n\n        // This has to be triggered to compute the \"out of stock\" feature and the hash variant changes\n        this.triggerVariantChange(this.el);\n\n        this._startZoom();\n\n        // Triggered when selecting a variant of a product in a carousel element\n        window.addEventListener('hashchange', (ev) => {\n            this._applySearch();\n            this.triggerVariantChange(this.el);\n        });\n\n        // This allows conditional styling for the filmstrip\n        const filmstripContainer = this.el.querySelector('#o_wsale_categories_filmstrip');\n        const filmstripWrapper = this.el.querySelector('.o_wsale_filmstrip_wrapper');\n        const isFilmstripScrollable = filmstripWrapper\n            ? filmstripWrapper.scrollWidth > filmstripWrapper.clientWidth\n            : false;\n\n        if (isBrowserFirefox() || hasTouch() || !isFilmstripScrollable) {\n            filmstripContainer?.classList.add('o_wsale_filmstrip_fancy_disabled');\n        }\n    }\n\n    destroy() {\n        this._cleanupZoom();\n    }\n\n    onMouseDown(ev) {\n        this.filmStripIsDown = true;\n        this.filmStripStartX = ev.pageX - ev.currentTarget.offsetLeft;\n        this.filmStripScrollLeft = ev.currentTarget.scrollLeft;\n        this.filmStripMoved = false;\n    }\n\n    onMouseLeave(ev) {\n        if (!this.filmStripIsDown) {\n            return;\n        }\n        ev.currentTarget.classList.remove('activeDrag');\n        this.filmStripIsDown = false\n    }\n\n    onMouseUp(ev) {\n        this.filmStripIsDown = false;\n        ev.currentTarget.classList.remove('activeDrag');\n    }\n\n    onMouseMove(ev) {\n        if (!this.filmStripIsDown) return;\n        ev.preventDefault();\n        ev.currentTarget.classList.add('activeDrag');\n        this.filmStripMoved = true;\n        const x = ev.pageX - ev.currentTarget.offsetLeft;\n        const walk = (x - this.filmStripStartX) * 2;\n        ev.currentTarget.scrollLeft = this.filmStripScrollLeft - walk;\n    }\n\n    onClickHandler(ev) {\n        if (this.filmStripMoved) {\n            ev.stopPropagation();\n            ev.preventDefault();\n        }\n    }\n\n    _applySearch() {\n        let params = new URLSearchParams(window.location.search);\n        let attributeValues = params.get('attribute_values')\n        if (!attributeValues) {\n            // TODO remove in 20 (or later): hash support of attribute values\n            params = new URLSearchParams(window.location.hash.substring(1));\n            attributeValues = params.get('attribute_values')\n        }\n        if (attributeValues) {\n            const attributeValueIds = attributeValues.split(',');\n            const inputs = document.querySelectorAll(\n                'input.js_variant_change, select.js_variant_change option'\n            );\n            let combinationChanged = false;\n            inputs.forEach((element) => {\n                if (attributeValueIds.includes(element.dataset.attributeValueId)) {\n                    if (element.tagName === 'INPUT' && !element.checked) {\n                        element.checked = true;\n                        combinationChanged = true;\n                    } else if (element.tagName === 'OPTION' && !element.selected) {\n                        element.selected = true;\n                        combinationChanged = true;\n                    }\n                }\n            });\n            if (combinationChanged) {\n                this._changeAttribute(\n                    '.css_attribute_color, [name=\"o_wsale_attribute_image_selector\"], .o_variant_pills'\n                );\n            }\n        }\n    }\n\n    /**\n     * Sets the url hash from the selected product options.\n     */\n    _setUrlHash() {\n        const inputs = document.querySelectorAll(\n            'input.js_variant_change:checked, select.js_variant_change option:checked'\n        );\n        let attributeIds = [];\n        inputs.forEach((element) => attributeIds.push(element.dataset.attributeValueId));\n        if (attributeIds.length > 0) {\n            const params = new URLSearchParams(window.location.search);\n            params.set('attribute_values', attributeIds.join(','))\n            // Avoid adding new entries in session history by replacing the current one\n            history.replaceState(null, '', url(window.location.pathname, Object.fromEntries(params)));\n        }\n    }\n\n    /**\n     * Set the checked values active.\n     *\n     * @param {String} selector - The selector matching the attributes to change.\n     */\n    _changeAttribute(selector) {\n        this.el.querySelectorAll(selector).forEach((el) => {\n            const input = el.querySelector('input');\n            const isActive = input?.checked;\n            el.classList.toggle('active', isActive);\n            if (isActive) input.dispatchEvent(new Event('change', { bubbles: true }));\n        });\n    }\n\n    _getProductImageLayout() {\n        return document.querySelector(\"#product_detail_main\").dataset.image_layout;\n    }\n\n    _getProductImageWidth() {\n        return document.querySelector(\"#product_detail_main\").dataset.image_width;\n    }\n\n    _getProductImageContainerSelector() {\n        return {\n            'carousel': \"#o-carousel-product\",\n            'grid': \"#o-grid-product\",\n        }[this._getProductImageLayout()];\n    }\n\n    _isEditorEnabled() {\n        return document.body.classList.contains(\"editor_enable\");\n    }\n\n    _startZoom() {\n        const salePage = document.querySelector(\".o_wsale_product_page\");\n        if (!salePage || this._getProductImageWidth() === \"none\") {\n            return;\n        }\n        this._cleanupZoom();\n        this.zoomCleanup = [];\n        // Zoom on click\n        if (salePage.dataset.ecomZoomClick) {\n            // In this case we want all the images not just the ones that are \"zoomables\"\n            const images = this.el.querySelectorAll('.product_detail_img');\n            for (const [idx, image] of images.entries()) {\n                const handler = () => {\n                    this.services.dialog.add(ProductImageViewer, {\n                        selectedImageIdx: idx,\n                        images,\n                        imageRatio: this.imageRatio,\n                    });\n                };\n                image.addEventListener(\"click\", handler);\n                this.zoomCleanup.push(() => {\n                    image.removeEventListener(\"click\", handler);\n                });\n            }\n        }\n    }\n\n    _cleanupZoom() {\n        if (!this.zoomCleanup || !this.zoomCleanup.length) {\n            return;\n        }\n        for (const cleanup of this.zoomCleanup) {\n            cleanup();\n        }\n        this.zoomCleanup = undefined;\n    }\n\n    /**\n     * On website, we display a carousel instead of only one image\n     */\n    _updateProductImage(productContainer, newImages) {\n        let images = productContainer.querySelector(this._getProductImageContainerSelector());\n        // When using the web editor, don't reload this or the images won't\n        // be able to be edited depending on if this is done loading before\n        // or after the editor is ready.\n        if (images && !this._isEditorEnabled()) {\n            images.insertAdjacentHTML('beforebegin', markup(newImages));\n            images.remove();\n\n            // Re-query the latest images.\n            images = productContainer.querySelector(this._getProductImageContainerSelector());\n            // Update the sharable image (only work for Pinterest).\n            const shareImageSrc = images.querySelector('img').src;\n            document.querySelector('meta[property=\"og:image\"]')\n                .setAttribute('content', shareImageSrc);\n\n            if (images.id === 'o-carousel-product') {\n                window.Carousel.getOrCreateInstance(images).to(0);\n            }\n            this._startZoom();\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    async onClickAdd(ev) {\n        const el = ev.currentTarget;\n        if (this.el.querySelector('.js_add_cart_variants')?.children?.length) {\n            await this.waitFor(this._getCombinationInfo(ev));\n            if (!ev.target.closest('.js_product').classList.contains('.css_not_available')) {\n                return this._addToCart(el);\n            }\n        } else {\n            return this._addToCart(el);\n        }\n    }\n\n    /**\n     * @param {HTMLElement} el\n     */\n    async _addToCart(el) {\n        const form = wSaleUtils.getClosestProductForm(el);\n        this._updateRootProduct(form);\n        const isBuyNow = el.classList.contains('o_we_buy_now');\n        const isConfigured = el.parentElement.id === 'add_to_cart_wrap';\n        const showQuantity = Boolean(el.dataset.showQuantity);\n        return this.services['cart'].add(this.rootProduct, {\n            isBuyNow: isBuyNow,\n            isConfigured: isConfigured,\n            showQuantity: showQuantity,\n        });\n    }\n\n    /**\n     * Event handler to increase or decrease quantity from the product page.\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeQuantity(ev) {\n        const input = ev.currentTarget.closest('.input-group').querySelector('input');\n        const min = parseFloat(input.dataset.min || 0);\n        const max = parseFloat(input.dataset.max || Infinity);\n        const previousQty = parseFloat(input.value || 0);\n        const quantity = (\n            ev.currentTarget.name === 'remove_one' ? -1 : 1\n        ) + previousQty;\n        const newQty = quantity > min ? (quantity < max ? quantity : max) : min;\n\n        if (newQty !== previousQty) {\n            input.value = newQty;\n            // Trigger `onChangeAddQuantity`.\n            input.dispatchEvent(new Event('change', { bubbles: true }));\n        }\n    }\n\n    /**\n     * Search attribute values based on the input text.\n     *\n     * @param {Event} ev\n     */\n    searchAttributeValues(ev) {\n        const input = ev.target;\n        const searchValue = input.value.toLowerCase();\n\n        document.querySelectorAll(`#${input.dataset.containerId} .form-check`).forEach(item => {\n            const labelText = item.querySelector('.form-check-label').textContent.toLowerCase();\n            item.style.display = labelText.includes(searchValue) ? '' : 'none'\n        });\n    }\n\n    /**\n     * Toggle the button text between \"View More\" and \"View Less\"\n     *\n     * @param {MouseEvent} ev\n     */\n    onToggleViewMoreLabel(ev) {\n        const button = ev.target;\n        const isExpanded = button.getAttribute('aria-expanded') === 'true';\n\n        button.innerHTML = isExpanded ? \"View Less\" : \"View More\";\n    }\n\n    /**\n     * When the quantity is changed, we need to query the new price of the product.\n     * Based on the pricelist, the price might change when quantity exceeds a certain amount.\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeAddQuantity(ev) {\n        const parent = wSaleUtils.getClosestProductForm(ev.currentTarget);\n        if (parent) this.triggerVariantChange(parent);\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    onChangeAttribute(ev) {\n        const productGrid = this.el.querySelector('.o_wsale_products_grid_table_wrapper');\n        if (productGrid) {\n            productGrid.classList.add('opacity-50');\n        }\n        const form = wSaleUtils.getClosestProductForm(ev.currentTarget);\n        const filters = form.querySelectorAll('input:checked, select');\n        const attributeValues = new Map();\n        const tags = new Set();\n        for (const filter of filters) {\n            if (filter.value) {\n                if (filter.name === 'attribute_value') {\n                    // Group attribute value ids by attribute id.\n                    const [attributeId, attributeValueId] = filter.value.split('-');\n                    const valueIds = attributeValues.get(attributeId) ?? new Set();\n                    valueIds.add(attributeValueId);\n                    attributeValues.set(attributeId, valueIds);\n                } else if (filter.name === 'tags') {\n                    tags.add(filter.value);\n                }\n            }\n        }\n        const url = new URL(form.action);\n        const searchParams = url.searchParams;\n        // Aggregate all attribute values belonging to the same attribute into a single\n        // `attribute_values` search param.\n        for (const entry of attributeValues.entries()) {\n            searchParams.append('attribute_values', `${entry[0]}-${[...entry[1]].join(',')}`);\n        }\n        // Aggregate all tags into a single `tags` search param.\n        if (tags.size) {\n            searchParams.set('tags', [...tags].join(','));\n        }\n        redirect(`${url.pathname}?${searchParams.toString()}`);\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    onSubmitSaleSearch(ev) {\n        if (!this.el.querySelector('.dropdown_sorty_by')) return;\n        const form = ev.currentTarget;\n        if (!ev.defaultPrevented && !form.matches('.disabled')) {\n            ev.preventDefault();\n            const url = new URL(form.action);\n            const searchParams = url.searchParams;\n            if (form.querySelector('[name=noFuzzy]')?.value === 'true') {\n                searchParams.set('noFuzzy', 'true');\n            }\n            const input = form.querySelector('input.search-query');\n            searchParams.set(input.name, input.value);\n            redirect(`${url.pathname}?${searchParams.toString()}`);\n        }\n    }\n\n    /**\n     * Toggles the disabled class on the parent element and the \"add to cart\" and \"buy now\" buttons\n     * depending on whether the current combination is possible.\n     *\n     * @param {Element} parent\n     * @param {boolean} isCombinationPossible\n     */\n    _toggleDisable(parent, isCombinationPossible) {\n        parent.classList.toggle('css_not_available', !isCombinationPossible);\n        parent.querySelector('#add_to_cart')?.classList?.toggle('disabled', !isCombinationPossible);\n        parent.querySelector('.o_we_buy_now')?.classList?.toggle('disabled', !isCombinationPossible);\n    }\n\n    /**\n     * When the variant is changed, this method will recompute:\n     * - Whether the selected combination is possible,\n     * - The extra price, if applicable,\n     * - The total price,\n     * - The display name of the product (e.g. \"Customizable desk (White, Steel)\"),\n     * - Whether a \"custom value\" input should be shown,\n     *\n     * \"Custom value\" changes are ignored since they don't change the combination.\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeVariant(ev) {\n        // Write the properties of the form elements in the DOM to prevent the current selection\n        // from being lost when activating the web editor.\n        const parent = ev.currentTarget.closest('.js_product');\n        parent.querySelectorAll('input').forEach(\n            el => el.checked ? el.setAttribute('checked', true) : el.removeAttribute('checked')\n        );\n        parent.querySelectorAll('select option').forEach(\n            el => el.selected ? el.setAttribute('selected', true) : el.removeAttribute('selected')\n        );\n\n        this._setUrlHash();\n\n        if (!parent.dataset.uniqueId) {\n            parent.dataset.uniqueId = uniqueId();\n        }\n        this._throttledGetCombinationInfo(this, parent.dataset.uniqueId)(ev);\n    }\n\n    onClickReviewsLink() {\n        Collapse.getOrCreateInstance(\n            document.querySelector('#o_product_page_reviews_content')\n        ).show();\n    }\n\n    /**\n     * Prevent multiple clicks on the confirm button when the form is submitted.\n     */\n    onClickConfirmOrder(ev) {\n        const button = ev.currentTarget.querySelector('button[type=\"submit\"]');\n        button.disabled = true;\n        // TODO(loti): \"random\" timeout seems brittle.\n        this.waitForTimeout(() => button.disabled = false, 5000);\n    }\n\n    /**\n     * Highlight selected color\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeColorAttribute(ev) {\n        const eventTarget = ev.target;\n        const parent = eventTarget.closest('.js_product');\n        parent.querySelectorAll('.css_attribute_color').forEach(\n            el => el.classList.toggle('active', el.matches(':has(input:checked)'))\n        );\n        const attrValueEl = eventTarget.closest('.variant_attribute')\n            ?.querySelector('.attribute_value');\n        if (attrValueEl) {\n            attrValueEl.innerText = eventTarget.dataset.valueName;\n        }\n    }\n\n    /**\n     * Highlight selected image\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeImageAttribute(ev) {\n        const parent = ev.target.closest('.js_product');\n        const images = parent.querySelectorAll('label[name=\"o_wsale_attribute_image_selector\"]');\n        images.forEach(el => el.classList.remove('active'));\n        images.forEach(el => {\n            const input = el.querySelector('input');\n            if (input && input.checked) {\n                el.classList.add('active');\n            }\n        });\n        const attrValueEl = ev.target\n            .closest('[name=\"variant_attribute\"]')?.querySelector('[name=\"attribute_value\"]');\n        if (attrValueEl) {\n            attrValueEl.innerText = ev.target.dataset.valueName;\n        }\n    }\n\n    onChangePillsAttribute(ev) {\n        const radio = ev.target.closest('.o_variant_pills').querySelector('input');\n        radio.click();  // Trigger onChangeVariant.\n        const parent = ev.target.closest('.js_product');\n        parent.querySelectorAll('.o_variant_pills').forEach(el => {\n            if (el.matches(':has(input:checked)')) {\n                el.classList.add(\n                    'active', 'border-primary', 'text-primary-emphasis', 'bg-primary-subtle'\n                );\n            } else {\n                el.classList.remove(\n                    'active', 'border-primary', 'text-primary-emphasis', 'bg-primary-subtle'\n                );\n            }\n        });\n    }\n\n    // -------------------------------------\n    // Utils\n    // -------------------------------------\n\n    /**\n     * Update the root product during based on the form elements.\n     *\n     * @param {HTMLFormElement} form - The form in which the product is.\n     */\n    _updateRootProduct(form) {\n        const productId = parseInt(\n            form.querySelector('input[type=\"hidden\"][name=\"product_id\"]')?.value\n        );\n        const productEl = form.closest('.js_product') ?? form;\n        const quantity = parseFloat(productEl.querySelector('input[name=\"add_qty\"]')?.value);\n        const uomId = this._getUoMId(form);\n        const isCombo = form.querySelector(\n            'input[type=\"hidden\"][name=\"product_type\"]'\n        )?.value === 'combo';\n        this.rootProduct = {\n            ...(productId ? {productId: productId} : {}),\n            productTemplateId: parseInt(form.querySelector(\n                'input[type=\"hidden\"][name=\"product_template_id\"]',\n            ).value),\n            ...(quantity ? {quantity: quantity} : {}),\n            ...(uomId ? {uomId: uomId} : {}),\n            ptavs: this._getSelectedPTAV(form),\n            productCustomAttributeValues: this._getCustomPTAVValues(form),\n            noVariantAttributeValues: this._getSelectedNoVariantPTAV(form),\n            ...(isCombo ? {isCombo: isCombo} : {}),\n        };\n    }\n\n    /**\n     * Return the selected stored PTAV(s) of in the provided form.\n     *\n     * @param {HTMLFormElement} form - The form in which the product is.\n     *\n     * @returns {Number[]} - The selected stored attribute(s), as a list of\n     *      `product.template.attribute.value` ids.\n     */\n    _getSelectedPTAV(form) {\n        const selectedPTAVElements = form.querySelectorAll([\n            '.js_product input.js_variant_change:not(.no_variant):checked',\n            '.js_product select.js_variant_change:not(.no_variant)'\n        ].join(','));\n        let selectedPTAV = [];\n        for(const el of selectedPTAVElements) {\n            selectedPTAV.push(parseInt(el.value));\n        }\n        return selectedPTAV;\n    }\n\n    /**\n     * Return the custom PTAV(s) values in the provided form.\n     *\n     * @param {HTMLFormElement} form - The form in which the product is.\n     *\n     * @returns {{id: number, value: string}[]} An array of objects where each object contains:\n     *      - `custom_product_template_attribute_value_id`: The ID of the custom attribute.\n     *      - `custom_value`: The value assigned to the custom attribute.\n     */\n    _getCustomPTAVValues(form) {\n        const customPTAVsValuesElements = form.querySelectorAll('.variant_custom_value');\n        let customPTAVsValues = [];\n        for(const el of customPTAVsValuesElements) {\n            customPTAVsValues.push({\n                'custom_product_template_attribute_value_id': parseInt(\n                    el.dataset.customProductTemplateAttributeValueId\n                ),\n                'custom_value': el.value,\n            });\n        }\n        return customPTAVsValues;\n    }\n\n    /**\n     * Return the selected non-stored PTAV(s) of the product in the provided form.\n     *\n     * @param {HTMLFormElement} form - The form in which the product is.\n     *\n     * @returns {Number[]} - The selected non-stored attribute(s), as a list of\n     *      `product.template.attribute.value` ids.\n     */\n    _getSelectedNoVariantPTAV(form) {\n        const selectedNoVariantPTAVElements = form.querySelectorAll([\n            'input.no_variant.js_variant_change:checked',\n            'select.no_variant.js_variant_change',\n        ].join(','));\n        let selectedNoVariantPTAV = [];\n        for(const el of selectedNoVariantPTAVElements) {\n            selectedNoVariantPTAV.push(parseInt(el.value));\n        }\n        return selectedNoVariantPTAV;\n    }\n}\n\n// TODO(loti): temporary hack. VariantMixin will be dropped.\nObject.assign(WebsiteSale.prototype, VariantMixin);\n\nregistry.category('public.interactions').add('website_sale.website_sale', WebsiteSale);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\n\nexport class AddToCartSnippet extends Interaction {\n    static selector = '.s_add_to_cart_btn';\n    dynamicContent = {\n        _root: { 't-on-click': this.onClickAddToCartButton },\n    };\n\n    async onClickAddToCartButton(ev) {\n        const dataset = ev.currentTarget.dataset;\n\n        const productTemplateId = parseInt(dataset.productTemplateId);\n        const productId = parseInt(dataset.productVariantId);\n        const isCombo = dataset.productType === 'combo';\n        const showQuantity = Boolean(dataset.showQuantity);\n        const action = dataset.action;\n\n        if (productId) {\n            const isAddToCartAllowed = await this.waitFor(rpc(\n                '/shop/product/is_add_to_cart_allowed', { product_id: productId }\n            ));\n            if (!isAddToCartAllowed) {\n                this.services.notification.add(\n                    _t(\"This product does not exist therefore it cannot be added to cart.\"),\n                    { title: _t(\"User Error\"), type: 'warning' }\n                );\n                return;\n            }\n        }\n\n        this.services['cart'].add({\n            productTemplateId: productTemplateId,\n            productId: productId,\n            isCombo: isCombo,\n        }, {\n            isBuyNow: action === 'buy_now',\n            showQuantity: showQuantity,\n        });\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.add_to_cart_snippet', AddToCartSnippet);\n", "import { _t } from '@web/core/l10n/translation';\nimport { registry } from '@web/core/registry';\nimport { utils as uiUtils } from '@web/core/ui/ui_service';\nimport { DynamicSnippet } from '@website/snippets/s_dynamic_snippet/dynamic_snippet';\n\n\nconst SIZE_CONFIG = {\n    small: { span: 2, row: '10vh' },\n    medium: { span: 2, row: '15vh' },\n    large: { span: 4, row: '15vh' },\n};\nconst ALIGNMENT_CLASSES_MAPPING = {\n    left: 'justify-content-between',\n    center: 'align_category_center',\n    right: 'justify-content-between align_category_right',\n};\n\nexport class DynamicSnippetCategory extends DynamicSnippet {\n    static selector = '.s_dynamic_snippet_category';\n\n    setup(){\n        super.setup();\n        this.templateKey = 'website_sale.s_dynamic_snippet_category.grid';\n        const nodeData = this.el.dataset;\n        nodeData.button = nodeData.button || _t(\"Explore Now\");\n        const colsCount = uiUtils.isSmall() ? 1 : parseInt(nodeData.columns);\n        const colSpanTwo = colsCount !== 1 && (nodeData.size !== 'small' || colsCount === 5);\n        // Pass custom data to the template.\n        nodeData.customTemplateData = JSON.stringify({\n            size: SIZE_CONFIG[nodeData.size]?.span,\n            alignmentClass: ALIGNMENT_CLASSES_MAPPING[nodeData.alignment],\n            buttonText: nodeData.button,\n            colSpanTwo: colSpanTwo,\n            includeParent: nodeData.parentCategoryId && nodeData.showParent,\n            parentCategoryId: parseInt(nodeData.parentCategoryId),\n        });\n    }\n\n    getQWebRenderOptions() {\n        const nodeData = this.el.dataset;\n        return Object.assign(super.getQWebRenderOptions(...arguments), {\n            colsCount: uiUtils.isSmall() ? 1 : parseInt(nodeData.columns),\n            rowSize: SIZE_CONFIG[nodeData.size].row,\n            gap: nodeData.gap,\n            rounded: nodeData.rounded,\n        });\n    }\n\n    getRpcParameters(){\n        return Object.assign(super.getRpcParameters(), {\n            parentId: parseInt(this.el.dataset.parentCategoryId),\n        });\n    }\n\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.dynamic_snippet_category', DynamicSnippetCategory);\n\nregistry\n    .category('public.interactions.edit')\n    .add('website_sale.dynamic_snippet_category', {Interaction: DynamicSnippetCategory});\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { rpc } from '@web/core/network/rpc';\n\nexport class CarouselProductCard extends Interaction {\n    static selector = '.o_carousel_product_card';\n    dynamicContent = {\n        '.js_add_cart': { 't-on-click': this.onClickAddToCart },\n        '.js_remove': { 't-on-click': this.onRemoveFromRecentlyViewed },\n    };\n\n    setup() {\n        this.add2cartRerender = this.el.dataset.add2cartRerender === 'True';\n    }\n\n    /**\n     * Event triggered by a click on the Add to cart button\n     *\n     * @param {Event} ev\n     */\n    async onClickAddToCart(ev) {\n        const dataset = ev.currentTarget.dataset;\n\n        const productTemplateId = parseInt(dataset.productTemplateId);\n        const productId = parseInt(dataset.productId);\n        const isCombo = dataset.productType === 'combo';\n        const showQuantity = Boolean(dataset.showQuantity);\n\n        await this.waitFor(this.services['cart'].add({\n            productTemplateId: productTemplateId,\n            productId: productId,\n            isCombo: isCombo,\n        }, {\n            showQuantity: showQuantity,\n        }));\n        if (this.add2cartRerender) {\n            const dynamicSnippetProducts = this.el.closest('.s_dynamic_snippet_products');\n            this.services['public.interactions'].stopInteractions(dynamicSnippetProducts);\n            this.services['public.interactions'].startInteractions(dynamicSnippetProducts);\n        }\n    }\n\n    /**\n     * Event triggered by a click on the remove button on a \"recently viewed\"\n     * template.\n     *\n     * @param {Event} ev\n     */\n    async onRemoveFromRecentlyViewed(ev) {\n        const rpcParams = {}\n        if (ev.currentTarget.dataset.productSelected) {\n            rpcParams.product_id = ev.currentTarget.dataset.productId;\n        } else {\n            rpcParams.product_template_id = ev.currentTarget.dataset.productTemplateId;\n        }\n        await this.waitFor(rpc('/shop/products/recently_viewed_delete', rpcParams));\n        const dynamicSnippetProducts = this.el.closest('.s_dynamic_snippet_products');\n        this.services['public.interactions'].stopInteractions(dynamicSnippetProducts);\n        this.services['public.interactions'].startInteractions(dynamicSnippetProducts);\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale.carousel_product_card', CarouselProductCard);\n", "import { DynamicSnippetCarousel } from \"@website/snippets/s_dynamic_snippet_carousel/dynamic_snippet_carousel\";\nimport { registry } from \"@web/core/registry\";\n\nexport class DynamicSnippetProducts extends DynamicSnippetCarousel {\n    static selector = \".s_dynamic_snippet_products\";\n\n    /**\n     * Gets the category search domain\n     */\n    getCategorySearchDomain() {\n        const searchDomain = [];\n        let productCategoryId = this.el.dataset.productCategoryId;\n        if (productCategoryId && productCategoryId !== \"all\") {\n            if (productCategoryId === \"current\") {\n                productCategoryId = undefined;\n                const productCategoryFieldEl = this.el.closest(\"body\").querySelector(\"#product_details .product_category_id\");\n                if (productCategoryFieldEl) {\n                    productCategoryId = parseInt(productCategoryFieldEl.value);\n                }\n                if (!productCategoryId) {\n                    const mainObject = this.services.website_page.mainObject;\n                    if (mainObject.model === \"product.public.category\") {\n                        productCategoryId = mainObject.id;\n                    }\n                }\n                if (!productCategoryId) {\n                    // Try with categories from product, unfortunately the category hierarchy is not matched with this approach\n                    const productTemplateIdEl = this.el.closest(\"body\").querySelector(\"#product_details .product_category_id\");\n                    if (productTemplateIdEl) {\n                        searchDomain.push([\"public_categ_ids.product_tmpl_ids\", \"=\", parseInt(productTemplateIdEl.value)]);\n                    }\n                }\n            }\n            if (productCategoryId) {\n                searchDomain.push([\"public_categ_ids\", \"child_of\", parseInt(productCategoryId)]);\n            }\n        }\n        return searchDomain;\n    }\n\n    getTagSearchDomain() {\n        const searchDomain = [];\n        let productTagIds = this.el.dataset.productTagIds;\n        productTagIds = productTagIds ? JSON.parse(productTagIds) : [];\n        if (productTagIds.length) {\n            searchDomain.push([\"all_product_tag_ids\", \"in\", productTagIds.map(productTag => productTag.id)]);\n        }\n        return searchDomain;\n    }\n\n    /**\n     * @override\n     */\n    getSearchDomain() {\n        const searchDomain = super.getSearchDomain(...arguments);\n        searchDomain.push(...this.getCategorySearchDomain());\n        searchDomain.push(...this.getTagSearchDomain());\n        const productNames = this.el.dataset.productNames;\n        if (productNames) {\n            const nameDomain = [];\n            for (const productName of productNames.split(\",\")) {\n                // Ignore empty names\n                if (!productName.length) {\n                    continue;\n                }\n                // Search on name, internal reference and barcode.\n                if (nameDomain.length) {\n                    nameDomain.unshift(\"|\");\n                }\n                nameDomain.push(...[\n                    \"|\", \"|\", [\"name\", \"ilike\", productName],\n                    [\"default_code\", \"=\", productName],\n                    [\"barcode\", \"=\", productName],\n                ]);\n            }\n            searchDomain.push(...nameDomain);\n        }\n        if (!this.el.dataset.showVariants) {\n            searchDomain.push(\"hide_variants\");\n        }\n        return searchDomain;\n    }\n\n    /**\n     * @override\n     */\n    getRpcParameters() {\n        const productTemplateIdEl = document.body.querySelector(\"#product_details .product_template_id\");\n        return Object.assign(super.getRpcParameters(...arguments), {\n            productTemplateId: productTemplateIdEl ? productTemplateIdEl.value : undefined,\n        });\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_sale.dynamic_snippet_products\", DynamicSnippetProducts);\n\nregistry\n    .category(\"public.interactions.edit\")\n    .add(\"website_sale.dynamic_snippet_products\", {\n        Interaction: DynamicSnippetProducts,\n    });\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { clickOnElement } from '@website/js/tours/tour_utils';\n\nexport function addToCart({\n    productName,\n    search = true,\n    productHasVariants = false,\n    expectUnloadPage = false,\n} = {}) {\n    const steps = [];\n    if (search) {\n        steps.push(...searchProduct(productName));\n    }\n    steps.push({\n        content: productName,\n        trigger: `a:contains(${productName})`,\n        run: \"click\",\n        expectUnloadPage,\n    });\n    steps.push({\n        content: \"Add to cart\",\n        trigger: \"#add_to_cart\",\n        run: \"click\",\n    });\n    if (productHasVariants) {\n        steps.push(clickOnElement('Continue Shopping', 'button:contains(\"Continue Shopping\")'));\n    }\n    return steps;\n}\n\nexport function assertCartAmounts({taxes = false, untaxed = false, total = false, delivery = false}) {\n    let steps = [];\n    if (taxes) {\n        steps.push({\n            content: 'Check if the tax is correct',\n            trigger: `tr[name=\"o_order_total_taxes\"] .oe_currency_value:text(${taxes})`,\n        });\n    }\n    if (untaxed) {\n        steps.push({\n            content: 'Check if the subtotal is correct',\n            trigger: `tr[name=\"o_order_total_untaxed\"] .oe_currency_value:text(${untaxed})`,\n        });\n    }\n    if (total) {\n        steps.push({\n            content: 'Check if the total is correct',\n            trigger: `tr[name=\"o_order_total\"] .oe_currency_value:text(${total})`,\n        });\n    }\n    if (delivery) {\n        steps.push({\n            content: 'Check if the delivery is correct',\n            trigger: `tr[name='o_order_delivery'] .oe_currency_value:text(${delivery})`,\n        });\n    }\n    return steps\n}\n\nexport function assertCartContains({productName, backend, notContains = false, combinationName = false} = {}) {\n    let trigger = `h6:contains(${productName})`;\n\n    if (notContains) {\n        trigger = `:not(${trigger})`;\n    }\n    let steps = [{\n        content: `Checking if ${productName} is in the cart`,\n        trigger: `${backend ? \":iframe\" : \"\"} ${trigger}`,\n    }];\n\n    if (combinationName) {\n        const combination_trigger = `span[class*=h6]:contains(${combinationName})`;\n        steps.push({\n            content: `Checking if ${combinationName} is the chosen combination in the cart`,\n            trigger: `${backend ? \":iframe\" : \"\"} ${combination_trigger}`,\n        })\n    }\n\n    return steps;\n}\n\n/**\n * Used to assert if the price attribute of a given product is correct on the /shop view\n */\nexport function assertProductPrice(attribute, value, productName) {\n    return {\n        content: `The ${attribute} of the ${productName} is ${value}`,\n        trigger: `div:contains(\"${productName}\") [data-oe-expression=\"template_price_vals['${attribute}']\"] .oe_currency_value:contains(\"${value}\")`,\n    };\n}\n\nexport function fillAdressForm(\n    adressParams = {\n        name: \"John Doe\",\n        phone: \"123456789\",\n        email: \"johndoe@gmail.com\",\n        street: \"1 rue de la paix\",\n        city: \"Paris\",\n        zip: \"75000\",\n    },\n    expectUnloadPage = false\n) {\n    const steps = [];\n    steps.push({\n        trigger: \"#o_country_id\",\n        run: \"selectByLabel Belgium\",\n    });\n    for (const arg of [\"name\", \"phone\", \"email\", \"street\", \"city\", \"zip\"]) {\n        steps.push({\n            content: `Address filling ${arg}`,\n            trigger: `form.address_autoformat input[name=${arg}]`,\n            run: `edit ${adressParams[arg]}`,\n        });\n    }\n    steps.push({\n        content: \"Continue checkout\",\n        trigger: \"a[name='website_sale_main_button']\",\n        run: \"click\",\n        expectUnloadPage,\n    });\n    return steps;\n}\n\nexport function goToCart({\n    quantity = 1,\n    position = \"bottom\",\n    backend = false,\n    expectUnloadPage = true,\n} = {}) {\n    return {\n        content: _t(\"Go to cart\"),\n        trigger: `${backend ? \":iframe\" : \"\"} a sup.my_cart_quantity:text(${quantity})`,\n        tooltipPosition: position,\n        run: \"click\",\n        expectUnloadPage,\n    };\n}\n\nexport function goToCheckout() {\n    return {\n        content: 'Checkout your order',\n        trigger: 'a[href^=\"/shop/checkout\"]',\n        run: 'click',\n        expectUnloadPage: true,\n    };\n}\n\nexport function confirmOrder() {\n    return {\n        content: 'Confirm',\n        trigger: 'a[href^=\"/shop/payment\"]',\n        run: 'click',\n        expectUnloadPage: true,\n    };\n}\n\nexport function pay({ expectUnloadPage = false, waitFinalizeYourPayment = false } = {}) {\n    const steps = [\n        {\n            content: 'Pay',\n            //Either there are multiple payment methods, and one is checked, either there is only one, and therefore there are no radio inputs\n            trigger: 'button[name=\"o_payment_submit_button\"]',\n            run: \"click\",\n            expectUnloadPage,\n        },\n    ];\n    if (waitFinalizeYourPayment) {\n        steps.push({\n            trigger: \"h1:contains(finalize your payment)\",\n            expectUnloadPage: true,\n        });\n    }\n    return steps;\n}\n\nexport function payWithDemo() {\n    return [{\n        content: 'eCommerce: add card number',\n        trigger: 'input[name=\"customer_input\"]',\n        run: \"edit 4242424242424242\",\n    },\n    ...pay({expectUnloadPage: true}),\n    {\n        content: 'eCommerce: check that the payment is successful',\n        trigger: '[name=\"order_confirmation\"]:contains(\"Your payment has been processed.\")',\n    }]\n}\n\nexport function payWithTransfer({\n    redirect = false,\n    expectUnloadPage = false,\n    waitFinalizeYourPayment = false,\n} = {}) {\n    const first_step = {\n        content: \"Select `Wire Transfer` payment method\",\n        trigger: 'input[name=\"o_payment_radio\"][data-payment-method-code=\"wire_transfer\"]',\n        run: \"click\",\n    }\n    if (!redirect) {\n        return [\n            first_step,\n            ...pay({ expectUnloadPage, waitFinalizeYourPayment }),\n            {\n                content: \"Last step\",\n                trigger:\n                    '[name=\"order_confirmation\"]:contains(\"Please use the following transfer details\")',\n                timeout: 30000,\n            },\n        ];\n    } else {\n        return [\n            first_step,\n            ...pay({ expectUnloadPage, waitFinalizeYourPayment }),\n            {\n                content: \"Last step\",\n                trigger:\n                    '[name=\"order_confirmation\"]:contains(\"Please use the following transfer details\")',\n                timeout: 30000,\n                run() {\n                    window.location.href = '/contactus'; // Redirect in JS to avoid the RPC loop (20x1sec)\n                },\n                expectUnloadPage: true,\n            },\n            {\n                content: \"wait page loaded\",\n                trigger: 'h1:contains(\"Contact us\")',\n            },\n        ];\n    }\n}\n\nexport function searchProduct(productName, { select = false } = {}) {\n    const steps = [\n        {\n            content: \"Search for the product\",\n            trigger: 'form input[name=\"search\"]',\n            run: `edit ${productName}`,\n        },\n        {\n            content: `Search ${productName}`,\n            trigger: `form:has(input[name=\"search\"]) .oe_search_button`,\n            run: \"click\",\n            expectUnloadPage: true,\n        },\n    ];\n    if (select) {\n        steps.push({\n            content: `Select ${productName}`,\n            trigger: `.oe_product_cart:first a:text(${productName})`,\n            run: \"click\",\n            expectUnloadPage: true,\n        });\n    }\n    return steps;\n}\n\n/**\n * Used to select a pricelist on the /shop view\n */\nexport function selectPriceList(pricelist) {\n    return [\n        {\n            content: \"Click on pricelist dropdown\",\n            trigger: \"div.o_pricelist_dropdown a[data-bs-toggle=dropdown]\",\n            run: \"click\",\n        },\n        {\n            content: \"Click on pricelist\",\n            trigger: `span:contains(${pricelist})`,\n            run: \"click\",\n            expectUnloadPage: true,\n        },\n    ];\n}\n\n/**\n * Used for resolving indeterministic behavior of tours\n */\nexport function waitForInteractionToLoad() {\n    return {\n        content: \"Wait for interaction to be ready\",\n        trigger: `body[is-ready=true]`,\n    };\n}\n", "import {\n    ComboConfiguratorDialog\n} from '@sale/js/combo_configurator_dialog/combo_configurator_dialog';\nimport { ProductCombo } from '@sale/js/models/product_combo';\nimport {\n    ProductConfiguratorDialog\n} from '@sale/js/product_configurator_dialog/product_configurator_dialog';\nimport { getSelectedCustomPtav, serializeComboItem } from '@sale/js/sale_utils';\nimport { browser } from '@web/core/browser/browser';\nimport { serializeDateTime } from '@web/core/l10n/dates';\nimport { rpc } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { redirect } from '@web/core/utils/urls';\nimport { session } from '@web/session';\n\nconst { DateTime } = luxon;\n\n/**\n * @typedef {Object} CustomAttributeValues\n * @property {Number} custom_product_template_attribute_value_id\n * @property {String} custom_value\n */\n\n/**\n * Manages product addition via the {@link addToCart} function.\n *\n * This function handles the process of adding products to the cart, including:\n * - Opening configurators if needed;\n * - Updating the cart with the selected products;\n * - Updating the cart count in the navbar;\n * - Notifying the customer of successful additions;\n * - Track the added products.\n *\n * Override this class to implement additional checks or\n * provide relevant information when adding a product to the cart.\n */\nexport class CartService {\n    static dependencies = ['cartNotificationService', 'dialog'];\n\n    /**\n     * Creates an instance of the service and initializes it using the {@link setup} method.\n     *\n     * The constructor delegates initialization to {@link setup} to handle wiring up dependencies,\n     * setting up methods, and initializing global variables, as constructors themselves cannot be\n     * patched.\n     *\n     * @returns {Object} - The initialized service object returned by {@link setup}.\n     */\n    constructor() {\n        return this.setup(...arguments);\n    }\n\n    /**\n     * Initializes the service wiring up dependencies, setting up methods and initializing global\n     * variables.\n     *\n     * @param {import(\"@web/env\").OdooEnv} _env - The environment object, not used here.\n     * @param {import(\"services\").ServiceFactories} services - An object containing instances of the\n     *      required services specified in the {@link dependencies} array.\n     *\n     * @returns {Object} - An object exposing the public methods of the service.\n     */\n    setup(_env, services) {\n        this.cartNotificationService = services.cartNotificationService;\n        this.dialog = services.dialog;\n        this.rpc = rpc;  // To be overridable in tests.\n\n        // Only expose `add` in the service registry.\n        return {\n            add: (...args) => this.add(...args)\n        };\n    }\n\n    //--------------------------------------------------------------------------\n    // Methods exposed by the service.\n    //--------------------------------------------------------------------------\n\n    /**\n     * Asynchronously adds a product to the shopping cart.\n     *\n     * @async\n     * @param {Object} product - The product details to add to the cart.\n     * @param {Number} product.productTemplateId - The product template's id, as a\n     *      `product.template` id.\n     * @param {Number} [product.productId=undefined] - The product's id, as a `product.product` id.\n     *      If not provided, selects the first available product or creates one if any attribute is\n     *      dynamic.\n     * @param {Number} [product.quantity=1] - The quantity of the product to add to the cart.\n     *      Defaults to 1.\n     * @param {Number} [product.uom_id=undefined] - The product's uom id, as a `uom.uom` id.\n     *      If not provided, considers the product default uom.\n     * @param {Number[]} [product.ptavs=[]] - The selected stored attribute(s), as a list of\n     *      `product.template.attribute.value` ids.\n     * @param {CustomAttributeValues[]} [product.productCustomAttributeValues=[]] - An\n     *      array of objects representing custom attribute values for the product.\n     * @param {Number[]} [product.noVariantAttributeValues=[]] - The selected non-stored\n     *      attribute(s), as a list of `product.template.attribute.value` ids.\n     * @param {Boolean} [product.isCombo=false] - Whether the product is part of a combo template.\n     *      Defaults to false.\n     * @param {...*} [product.rest] - Locally unused data sent to the controllers.\n     * @param {Object} [options] - Define how to add products to the cart.\n     * @param {Boolean} [options.isBuyNow=false] - Whether the product should be added immediately,\n     *      bypassing optional configurations. Defaults to false.\n     * @param {Boolean} [options.redirectToCart=true] - When `isBuyNow` is `true`, whether to\n     *      redirect the customer to the cart. Defaults to true.\n     * @param {Boolean} [options.isConfigured=false] - Whether the product is already configured.\n     *      Defaults to false.\n     * @param {Boolean} [options.showQuantity=true] - Whether quantity selector should be shown\n     *      Defaults to true.\n     * @returns {Number} - The product's quantity added to the cart.\n     */\n    async add({\n            productTemplateId,\n            productId = undefined,\n            quantity = 1,\n            uomId = undefined,\n            ptavs = [],\n            productCustomAttributeValues = [],\n            noVariantAttributeValues = [],\n            isCombo = false,\n            ...rest\n        },\n        {\n            isBuyNow=false,\n            redirectToCart=true,\n            isConfigured=false,\n            showQuantity=true,\n        } = {},\n    ) {\n        if (!productId && ptavs.length) {\n            productId = await this.rpc('/sale/create_product_variant', {\n                product_template_id: productTemplateId,\n                product_template_attribute_value_ids: ptavs.concat(noVariantAttributeValues),\n            })\n        }\n\n        if(isCombo) {\n            const { combos, ...remainingData } = await this.rpc(\n                '/website_sale/combo_configurator/get_data',\n                {\n                    product_tmpl_id: productTemplateId,\n                    quantity: quantity,\n                    // NOTE: no uom for combos on purpose\n                    date: serializeDateTime(DateTime.now()),\n                    ...rest\n                }\n            );\n            const preselectedComboItems = combos\n                 .map(combo => new ProductCombo(combo))\n                 .map(combo => combo.preselectedComboItem)\n                 .filter(Boolean);\n            // If the combo product is already fully configured (i.e. a combo item has been\n            // preselected for each combo choice), then it can be added to the cart without\n            // opening the combo configurator.\n            if (preselectedComboItems.length === combos.length) {\n                return this._makeRequest({\n                    productTemplateId: productTemplateId,\n                    productId: productId,\n                    quantity: remainingData.quantity,\n                    uomId: uomId,\n                    linked_products: preselectedComboItems.map(\n                        (comboItem) => this._serializeComboItem(\n                            comboItem, productTemplateId, remainingData.quantity\n                        )\n                    ),\n                    shouldRedirectToCart: isBuyNow && redirectToCart,\n                    ...rest\n                });\n            }\n            // If some combo choices need to be configured, open the combo configurator.\n            return this._openComboConfigurator(\n                productTemplateId,\n                productId,\n                combos.map(combo => new ProductCombo(combo)),\n                remainingData,\n                {\n                    isBuyNow: isBuyNow,\n                    showQuantity: showQuantity,\n                },\n                rest\n            );\n        }\n\n        if (isBuyNow) {\n            return this._makeRequest({\n                productTemplateId,\n                productId,\n                quantity,\n                uomId,\n                productCustomAttributeValues,\n                noVariantAttributeValues,\n                shouldRedirectToCart: isBuyNow && redirectToCart,\n                ...rest\n            });\n        }\n\n        const shouldShowProductConfigurator = await this.rpc(\n            '/website_sale/should_show_product_configurator',\n            {\n                product_template_id: productTemplateId,\n                ptav_ids: ptavs,\n                is_product_configured: isConfigured,\n            }\n        );\n        if (shouldShowProductConfigurator) {\n            return this._openProductConfigurator(\n                productTemplateId,\n                quantity,\n                uomId,\n                ptavs.concat(noVariantAttributeValues),\n                productCustomAttributeValues,\n                {\n                    isBuyNow: isBuyNow,\n                    isMainProductConfigurable: !isConfigured,\n                    showQuantity: showQuantity,\n                },\n                rest\n            );\n        }\n\n        return this._makeRequest({\n            productTemplateId,\n            productId,\n            quantity,\n            uomId,\n            productCustomAttributeValues,\n            noVariantAttributeValues,\n            shouldRedirectToCart: isBuyNow && redirectToCart,\n            ...rest\n        });\n    }\n\n    //--------------------------------------------------------------------------\n    // Configurators\n    //--------------------------------------------------------------------------\n\n    /**\n     * Opens the combo configurator dialog.\n     *\n     * @private\n     * @param {Number} productTemplateId - The product template id, as a `product.template` id.\n     * @param {Number} productId - The product's id, as a `product.product` id.\n     * @param {ProductCombo[]} combos - The combos of the product.\n     * @param {Object} remainingData - Other data needed to open the combo configurator.\n     * @param {Number} remainingData.currency_id - The currency's id, as a `res.currency` id.\n     * @param {String} remainingData.display_name - The name of the combo.\n     * @param {Number} remainingData.price - The price of the combo.\n     * @param {Number} remainingData.product_tmpl_id - The product template's id, as a\n     *      `product.template` id.\n     * @param {Number} remainingData.quantity - The quantity of the combo.\n     * @param {Object} [options] - Define how to add products to the cart.\n     * @param {Boolean} [options.isBuyNow] - Whether the product should be added immediately,\n     *      bypassing optional configurations.\n     * @param {Object} [additionalData] - Additional data sent to the controllers.\n     *\n     * @returns {Number} - The product's quantity added to the cart.\n     */\n    async _openComboConfigurator(\n        productTemplateId,\n        productId,\n        combos,\n        remainingData,\n        options,\n        additionalData\n    ) {\n        return await new Promise((resolve) => {\n            this.dialog.add(ComboConfiguratorDialog, {\n                combos: combos,\n                ...remainingData,\n                date: serializeDateTime(DateTime.now()),\n                edit: false,\n                isFrontend: true,\n                options,\n                ...additionalData,\n                save: async (comboProductData, selectedComboItems, options) => {\n                    resolve(this._makeRequest({\n                        productTemplateId: productTemplateId,\n                        productId: productId,\n                        quantity: comboProductData.quantity,\n                        // NOTE: no uom since not handled in combo configurator\n                        linked_products: selectedComboItems.map(\n                            (comboItem) => this._serializeComboItem(\n                                comboItem, productTemplateId, comboProductData.quantity\n                            )\n                        ),\n                        shouldRedirectToCart: options.goToCart,\n                        ...additionalData,\n                    }));\n                },\n                discard: () => resolve(0),\n            });\n        });\n    }\n\n    /**\n     * Opens the product configurator dialog.\n     *\n     * @private\n     * @param {Number} productTemplateId - The product template id, as a `product.template` id.\n     * @param {Number} quantity - The quantity to add to the cart.\n     * @param {Number[]} combination - The combination of the product, as a list of\n     *      `product.template.attribute.value` ids.\n     * @param {CustomAttributeValues[]} productCustomAttributeValues - An array of objects\n     *      representing custom attribute values for the product.\n     * @param {Object} [options] - Define how to add products to the cart.\n     * @param {Boolean} [options.isBuyNow] - Whether the product should be added immediately,\n     *      bypassing optional configurations.\n     * @param {Boolean} [options.isMainProductConfigurable] - Whether the product should be\n     *      configurable.\n     * @param {Object} [additionalData] - Additional data sent to the controllers.\n     *\n     * @returns {Number} - The product's quantity added to the cart.\n     */\n    async _openProductConfigurator(\n        productTemplateId,\n        quantity,\n        uomId,\n        combination,\n        productCustomAttributeValues,\n        options,\n        additionalData\n    ) {\n        return await new Promise((resolve) => {\n            this.dialog.add(ProductConfiguratorDialog, {\n                productTemplateId: productTemplateId,\n                ptavIds: combination,\n                customPtavs: productCustomAttributeValues.map(customPtav => ({\n                    id: customPtav.custom_product_template_attribute_value_id,\n                    value: customPtav.custom_value,\n                })),\n                quantity: quantity,\n                productUOMId: uomId,\n                soDate: serializeDateTime(DateTime.now()),\n                edit: false,\n                isFrontend: true,\n                selectedComboItems: [],  // optional products for combo aren't supported on ecommerce for now.\n                options,\n                ...additionalData,\n                save: async (mainProduct, optionalProducts, options) => {\n                    const product = this._serializeProduct(mainProduct);\n                    resolve(this._makeRequest({\n                        productTemplateId: product.product_template_id,\n                        productId: product.product_id,\n                        quantity: product.quantity,\n                        uom_id: product.uom_id,\n                        productCustomAttributeValues: product.product_custom_attribute_values,\n                        noVariantAttributeValues: product.no_variant_attribute_value_ids,\n                        linked_products: optionalProducts.map(this._serializeProduct),\n                        shouldRedirectToCart: options.goToCart,\n                        ...additionalData,\n                    }));\n                },\n                discard: () => resolve(0),\n            });\n        });\n    }\n\n    /**\n     * Serialize a product into a format understandable by the server.\n     *\n     * @private\n     * @param {Object} product - The product to serialize.\n     *\n     * @returns {Object} - The serialized product.\n     */\n    _serializeProduct(product) {\n        let serializedProduct = {\n            product_id: product.id,\n            product_template_id: product.product_tmpl_id,\n            parent_product_template_id: product.parent_product_tmpl_id,\n            quantity: product.quantity,\n            uom_id: product.uom.id,\n        }\n\n        if (!product.attribute_lines) {\n            return serializedProduct;\n        }\n\n        // Custom attributes.\n        serializedProduct.product_custom_attribute_values = [];\n        for (const ptal of product.attribute_lines) {\n            const selectedCustomPtav = getSelectedCustomPtav(ptal);\n            if (selectedCustomPtav) {\n                serializedProduct.product_custom_attribute_values.push({\n                    custom_product_template_attribute_value_id: selectedCustomPtav.id,\n                    custom_value: ptal.customValue ?? '',\n                });\n            }\n        }\n\n        // No variant attributes.\n        serializedProduct.no_variant_attribute_value_ids = product.attribute_lines\n            .filter(ptal => ptal.create_variant === 'no_variant')\n            .flatMap(ptal => ptal.selected_attribute_value_ids);\n\n        return serializedProduct;\n    }\n\n    /**\n     * Serialize a combo item into a format understandable by the server.\n     *\n     * @private\n     * @param {ProductComboItem} comboItem - The combo item to serialize.\n     * @param {Number} parentProductTemplateId - The parent's product template id, as a\n     *      `product.template` id.\n     * @param {Number} quantity - The quantity to add to the cart.\n     *\n     * @returns {Object} - The serialized combo item.\n     */\n    _serializeComboItem(comboItem, parentProductTemplateId, quantity) {\n        return {\n            product_template_id: comboItem.product.product_tmpl_id,\n            parent_product_template_id: parentProductTemplateId,\n            quantity: quantity,\n            ...serializeComboItem(comboItem),\n        };\n    }\n\n    //--------------------------------------------------------------------------\n    // Helpers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Make a request to the server to add the product to the cart.\n     *\n     * @async\n     * @private\n     * @param {Object} data - Data containing product(s) to add to the cart and options for adding\n     *      them.\n     * @param {Number} data.productTemplateId - The product template's id, as a\n     *      `product.template` id.\n     * @param {Number} data.productId - The product's id, as a `product.product` id.\n     * @param {Number} data.uomId - The uom's id, as a `uom.uom` id.\n     * @param {Number} data.quantity - The quantity of the product to add to the cart.\n     * @param {CustomAttributeValues[]} [data.productCustomAttributeValues=[]] - An\n     *      array of objects representing custom attribute values for the product.\n     * @param {Number[]} [data.noVariantAttributeValues=[]] - The selected non-stored\n     *      attribute(s), as a list of `product.template.attribute.value` ids.\n     * @param {Boolean} [data.shouldRedirectToCart=false] - Whether to redirect the\n     *      customer to the cart. Defaults to false.\n     * @param {...*} [data.rest] - Locally unused data sent to the controllers.\n     *\n     * @returns {Number} - The product's quantity in the cart.\n     */\n    async _makeRequest({\n        productTemplateId,\n        productId,\n        quantity,\n        uomId=undefined,\n        productCustomAttributeValues=[],\n        noVariantAttributeValues=[],\n        shouldRedirectToCart=false,\n        ...rest\n    }) {\n        const data = await this.rpc('/shop/cart/add', {\n            product_template_id: productTemplateId,\n            product_id: productId,\n            quantity: quantity,\n            uom_id: uomId,\n            product_custom_attribute_values: productCustomAttributeValues,\n            no_variant_attribute_value_ids: noVariantAttributeValues,\n            ...rest\n        });\n        // TODO should not redirect if errors in data.\n        if (shouldRedirectToCart || session.add_to_cart_action === 'go_to_cart') {\n            redirect('/shop/cart');\n            return data.quantity;\n        }\n        if (data.cart_quantity && (\n            data.cart_quantity !== browser.sessionStorage.getItem('website_sale_cart_quantity')\n        )) {\n            this._updateCartIcon(data.cart_quantity);\n        };\n        this._showCartNotification(data.notification_info);\n        if (data.quantity) {\n            this._trackProducts(data.tracking_info);\n        }\n        return data.quantity;\n    }\n\n    /**\n     * Update the quantity on the cart icon in the navbar.\n     *\n     * @private\n     * @param {Number} cartQuantity - The number of items currently in the cart.\n     *\n     * @returns {void}\n     */\n    _updateCartIcon(cartQuantity) {\n        browser.sessionStorage.setItem('website_sale_cart_quantity', cartQuantity);\n        // Mobile and Desktop elements have to be updated.\n        const cartQuantityElements = document.querySelectorAll('.my_cart_quantity');\n        for(const cartQuantityElement of cartQuantityElements) {\n            if (cartQuantity === 0) {\n                cartQuantityElement.classList.add('d-none');\n            } else {\n                const cartIconElement = document.querySelector('li.o_wsale_my_cart');\n                cartIconElement.classList.remove('d-none');\n                cartQuantityElement.classList.remove('d-none');\n                cartQuantityElement.classList.add('o_mycart_zoom_animation');\n                setTimeout(() => {\n                    cartQuantityElement.textContent = cartQuantity;\n                    cartQuantityElement.classList.remove('o_mycart_zoom_animation');\n                }, 300);\n            }\n        }\n    }\n\n    /**\n     * Show the notification about the cart.\n     *\n     * @private\n     * @param {Object} props\n     * @param {Object} options\n     *\n     * @returns {void}\n     */\n    _showCartNotification(props, options = {}) {\n        if (props.lines) {\n            this.cartNotificationService.add('', {\n                lines: props.lines,\n                currency_id: props.currency_id,\n                ...options,\n            });\n        }\n        if (props.warning) {\n            this.cartNotificationService.add('', {\n                warning: props.warning,\n                ...options,\n            });\n        }\n    }\n\n    /**\n     * Track the products added to the cart.\n     *\n     * @private\n     * @param {Object[]} trackingInfo - A list of product tracking information.\n     *\n     * @returns {void}\n     */\n    _trackProducts(trackingInfo) {\n        document.querySelector('.oe_website_sale').dispatchEvent(\n            new CustomEvent('add_to_cart_event', {'detail': trackingInfo})\n        );\n    }\n}\n\nexport const cartService = {\n    dependencies: CartService.dependencies,\n    async: ['add'],\n    start(env, dependencies) {\n        return new CartService(env, dependencies);\n    },\n}\n\nregistry.category('services').add('cart', cartService);\n", "import { localization } from '@web/core/l10n/localization';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport { KeepLast } from '@web/core/utils/concurrency';\nimport { memoize } from '@web/core/utils/functions';\nimport { insertThousandsSep } from '@web/core/utils/numbers';\nimport { throttleForAnimation } from '@web/core/utils/timing';\nimport { markup } from '@odoo/owl';\nimport wSaleUtils from '@website_sale/js/website_sale_utils';\n\nconst VariantMixin = {\n    /**\n     * @see onChangeVariant\n     *\n     * @private\n     * @param {Event} ev\n     * @returns {Deferred}\n     */\n    async _getCombinationInfo(ev) {\n        if (ev.target.classList.contains('variant_custom_value')) return Promise.resolve();\n        const parent = ev.target.closest('.js_product');\n        if (!parent) return Promise.resolve();\n        const combination = wSaleUtils.getSelectedAttributeValues(parent);\n\n        const combinationInfo = await this.waitFor(rpc('/website_sale/get_combination_info', {\n            'product_template_id': parseInt(parent.querySelector('.product_template_id')?.value),\n            'product_id': this._getProductId(parent),\n            'combination': combination,\n            'add_qty': parseInt(parent.querySelector('input[name=\"add_qty\"]')?.value),\n            'uom_id': this._getUoMId(parent),\n            'context': this.context,\n            ...this._getOptionalCombinationInfoParam(parent),\n        }));\n        this._onChangeCombination(ev, parent, combinationInfo);\n        this._checkExclusions(parent, combination);\n    },\n\n    _getUoMId(element) {\n        return parseInt(element.querySelector('input[name=\"uom_id\"]:checked')?.value)\n    },\n\n    /**\n     * Hook to add optional info to the combination info call.\n     *\n     * @param {Element} product\n     */\n    _getOptionalCombinationInfoParam(product) {\n        return {};\n    },\n\n    /**\n     * Will add the \"custom value\" input for this attribute value if\n     * the attribute value is configured as \"custom\" (see product_attribute_value.is_custom)\n     *\n     * @param {Element} el\n     */\n    handleCustomValues(el) {\n        let variantContainer;\n        let customInput = false;\n        if (el.matches('input[type=radio]:checked')) {\n            variantContainer = el.closest('ul').closest('li');\n            customInput = el;\n        } else if (el.matches('select')) {\n            variantContainer = el.closest('li');\n            customInput = el.querySelector(`option[value=\"${el.value}\"]`);\n        }\n\n        if (variantContainer) {\n            const customValue = variantContainer.querySelector('.variant_custom_value');\n            if (customInput && customInput.dataset.isCustom === 'True') {\n                const attributeValueId = customInput.dataset.valueId;\n                if (\n                    !customValue\n                    || customValue.dataset.customProductTemplateAttributeValueId !== attributeValueId\n                ) {\n                    customValue?.remove();\n\n                    const previousCustomValue = customInput.getAttribute('previous_custom_value');\n                    const input = document.createElement('input');\n                    input.type = 'text';\n                    input.dataset.customProductTemplateAttributeValueId = attributeValueId;\n                    input.classList.add(\n                        'variant_custom_value', 'custom_value_radio', 'form-control', 'mt-2'\n                    );\n                    input.setAttribute('placeholder', customInput.dataset.valueName);\n                    variantContainer.appendChild(input);\n                    if (previousCustomValue) {\n                        input.value = previousCustomValue;\n                    }\n                }\n            } else {\n                customValue?.remove();\n            }\n        }\n    },\n\n    /**\n     * Triggers the price computation and other variant specific changes\n     *\n     * @param {Element} container\n     */\n    triggerVariantChange(container) {\n        container.querySelectorAll('ul[data-attribute-exclusions]')\n            .forEach((el) => el.dispatchEvent(new Event('change')));\n        container.querySelectorAll('input.js_variant_change:checked, select.js_variant_change')\n            .forEach((el) => this.handleCustomValues(el));\n    },\n\n    /**\n     * Will disable attribute value's inputs based on combination exclusions\n     * and will disable the \"add\" button if the selected combination\n     * is not available\n     *\n     * This will check both the exclusions within the product itself and\n     * the exclusions coming from the parent product (meaning that this product\n     * is an option of the parent product)\n     *\n     * It will also check that the selected combination does not exactly\n     * match a manually archived product\n     *\n     * @private\n     * @param {Element} parent the parent container to apply exclusions\n     * @param {Array} combination the selected combination of product attribute values\n     */\n    _checkExclusions(parent, combination) {\n        const combinationDataJson = parent.querySelector('ul[data-attribute-exclusions]')\n            .dataset.attributeExclusions;\n        const combinationData = combinationDataJson ? JSON.parse(combinationDataJson) : {};\n\n        parent.querySelectorAll('option, input, label, .o_variant_pills').forEach(el => {\n            el.classList.remove('css_not_available');\n        });\n        parent.querySelectorAll('option, input').forEach(el => {\n            const li = el.closest('li');\n            if (li) {\n                li.removeAttribute('title');\n                li.dataset.excludedBy = '';\n            }\n        });\n        // exclusion rules: array of ptav\n        // for each of them, contains array with the other ptav they exclude\n        if (combinationData.exclusions) {\n            // browse all the currently selected attributes\n            Object.values(combination).forEach((current_ptav) => {\n                if (combinationData.exclusions.hasOwnProperty(current_ptav)) {\n                    // for each exclusion of the current attribute:\n                    Object.values(combinationData.exclusions[current_ptav]).forEach((excluded_ptav) => {\n                        // disable the excluded input (even when not already selected)\n                        // to give a visual feedback before click\n                        this._disableInput(\n                            parent,\n                            excluded_ptav,\n                            current_ptav,\n                            combinationData.mapped_attribute_names\n                        );\n                    });\n                }\n            });\n        }\n        // combination exclusions: array of array of ptav\n        // for example a product with 3 attributes of which 1 combination is unavailable (archived)\n        // requires the first 2 to be selected for the third to be grayed out\n        if (combinationData.archived_combinations) {\n            combinationData.archived_combinations.forEach((excludedCombination) => {\n                const ptavCommon = excludedCombination.filter((ptav) => combination.includes(ptav));\n                if (\n                    !!ptavCommon\n                    && (combination.length === excludedCombination.length)\n                    && (ptavCommon.length === combination.length)\n                ) {\n                    // Selected combination is archived, all attributes must be disabled from each other\n                    combination.forEach((ptav) => {\n                        combination.forEach((ptavOther) => {\n                            if (ptav === ptavOther) {\n                                return;\n                            }\n                            this._disableInput(\n                                parent,\n                                ptav,\n                                ptavOther,\n                                combinationData.mapped_attribute_names,\n                            );\n                        });\n                    });\n                } else if (\n                    !!ptavCommon\n                    && (combination.length === excludedCombination.length)\n                    && (ptavCommon.length === (combination.length - 1))\n                ) {\n                    // In this case we only need to disable the remaining ptav\n                    const unavailablePtav = excludedCombination.find(\n                        (ptav) => !combination.includes(ptav)\n                    );\n                    excludedCombination.forEach((ptav) => {\n                        if (ptav === unavailablePtav) {\n                            return;\n                        }\n                        this._disableInput(\n                            parent,\n                            unavailablePtav,\n                            ptav,\n                            combinationData.mapped_attribute_names,\n                        );\n                    });\n                }\n            });\n        }\n    },\n\n    /**\n     * Extracted to a method to be extendable by other modules\n     *\n     * @param {Element} parent\n     */\n    _getProductId(parent) {\n        return parseInt(parent.querySelector('.product_id').value);\n    },\n\n    /**\n     * Will gray out the input/option that refers to the passed attributeValueId.\n     * This is used for showing the user that some combinations are not available.\n     *\n     * It will also display a message explaining why the input is not selectable.\n     * Based on the \"excludedBy\" and the \"productName\" params.\n     * e.g: Not available with Color: Black\n     *\n     * @private\n     * @param {Element} parent\n     * @param {integer} attributeValueId\n     * @param {integer} excludedBy The attribute value that excludes this input\n     * @param {Object} attributeNames A dict containing all the names of the attribute values\n     *   to show a human readable message explaining why the input is grayed out.\n     * @param {string} [productName] The parent product. If provided, it will be appended before\n     *   the name of the attribute value that excludes this input\n     *   e.g: Not available with Customizable Desk (Color: Black)\n     */\n    _disableInput(parent, attributeValueId, excludedBy, attributeNames, productName) {\n        const input = parent.querySelector(\n            `option[value=\"${attributeValueId}\"], input[value=\"${attributeValueId}\"]`\n        );\n        input.classList.add('css_not_available')\n        input.closest('label')?.classList?.add('css_not_available');\n        input.closest('.o_variant_pills')?.classList?.add('css_not_available');\n\n        const li = input.closest('li');\n\n        if (li && excludedBy && attributeNames) {\n            const excludedByData = li.dataset.excludedBy ? li.dataset.excludedBy.split(',') : [];\n\n            let excludedByName = attributeNames[excludedBy];\n            if (productName) {\n                excludedByName = `${productName} (${excludedByName})`;\n            }\n            excludedByData.push(excludedByName);\n\n            li.setAttribute('title', _t(\"Not available with %s\", excludedByData.join(', ')));\n            li.dataset.excludedBy = excludedByData.join(',');\n        }\n    },\n\n    /**\n     * @see onChangeVariant\n     *\n     * @private\n     * @param {MouseEvent} ev\n     * @param {Element} parent\n     * @param {Array} combination\n     */\n    _onChangeCombination(ev, parent, combination) {\n        const isCombinationPossible = !!combination.is_combination_possible;\n        const precision = combination.currency_precision;\n        const productPrice = parent.querySelector('.product_price');\n        if (productPrice && !productPrice.classList.contains('decimal_precision')) {\n            productPrice.classList.add('decimal_precision');\n            productPrice.dataset.precision = precision;\n        }\n        const pricePerUom = parent.querySelector('.o_base_unit_price')\n            ?.querySelector('.oe_currency_value');\n        if (pricePerUom) {\n            const hasPrice = isCombinationPossible && combination.base_unit_price !== 0;\n            pricePerUom.closest('.o_base_unit_price_wrapper').classList.toggle('d-none', !hasPrice);\n            if (hasPrice) {\n                pricePerUom.textContent = this._priceToStr(combination.base_unit_price, precision);\n                const unit = parent.querySelector('.oe_custom_base_unit');\n                if (unit) {\n                    unit.textContent = combination.base_unit_name;\n                }\n            }\n        }\n\n        // Triggers a new JS event with the correct payload, which is then handled\n        // by the google analytics tracking code.\n        // Indeed, every time another variant is selected, a new view_item event\n        // needs to be tracked by google analytics.\n        if ('product_tracking_info' in combination) {\n            const product = document.querySelector('#product_detail');\n            product.dispatchEvent(\n                new CustomEvent('view_item_event', { 'detail': combination['product_tracking_info'] })\n            );\n        }\n        const addToCart = parent.querySelector('#add_to_cart_wrap');\n        const contactUsButton = parent.closest('#product_details')\n            ?.querySelector('#contact_us_wrapper');\n        const quantity = parent.querySelector('.css_quantity');\n        const productUnavailable = parent.querySelector('#product_unavailable');\n\n        const preventSale = combination.prevent_zero_price_sale;\n        productPrice?.classList?.toggle('d-inline-block', !preventSale);\n        productPrice?.classList?.toggle('d-none', preventSale);\n        quantity?.classList?.toggle('d-inline-flex', !preventSale);\n        quantity?.classList?.toggle('d-none', preventSale);\n        addToCart?.classList?.toggle('d-inline-flex', !preventSale);\n        addToCart?.classList?.toggle('d-none', preventSale);\n        contactUsButton?.classList?.toggle('d-none', !preventSale);\n        contactUsButton?.classList?.toggle('d-flex', preventSale);\n        productUnavailable?.classList?.toggle('d-none', !preventSale);\n        productUnavailable?.classList?.toggle('d-flex', preventSale);\n\n        if (contactUsButton) {\n            const contactUsButtonLink = contactUsButton.querySelector('a');\n            const url = contactUsButtonLink.getAttribute('data-url');\n            contactUsButtonLink.setAttribute('href', `${url}?subject=${combination.display_name}`);\n        }\n\n        const price = parent.querySelector('.oe_price')?.querySelector('.oe_currency_value');\n        const defaultPrice = parent.querySelector('.oe_default_price')\n            ?.querySelector('.oe_currency_value');\n        const comparePrice = parent.querySelector('.oe_compare_list_price');\n        if (price) {\n            price.textContent = this._priceToStr(combination.price, precision);\n        }\n        if (defaultPrice) {\n            defaultPrice.textContent = this._priceToStr(combination.list_price, precision);\n            defaultPrice.closest('.oe_website_sale').classList\n                .toggle('discount', combination.has_discounted_price);\n            defaultPrice.parentElement.classList\n                .toggle('d-none', !combination.has_discounted_price);\n        }\n        if (comparePrice) {\n            comparePrice.classList.toggle('d-none', combination.has_discounted_price);\n        }\n\n        this._toggleDisable(parent, isCombinationPossible);\n\n        // update images & tags only when changing product\n        // or when either ids are 'false', meaning dynamic products.\n        // Dynamic products don't have images BUT they may have invalid\n        // combinations that need to disable the image.\n        if (!combination.no_product_change) {\n            this._updateProductImage(\n                parent.closest('tr.js_product, .oe_website_sale'), combination.carousel\n            );\n            const productTags = parent.querySelector('.o_product_tags');\n            productTags?.insertAdjacentHTML('beforebegin', markup(combination.product_tags));\n            productTags?.remove();\n        }\n\n        const productIdInput = parent.querySelector('.product_id');\n        productIdInput.value = combination.product_id || 0;\n        productIdInput.dispatchEvent(new Event('change', { bubbles: true }));\n\n        this.handleCustomValues(ev.target);\n    },\n\n    /**\n     * returns the formatted price\n     *\n     * @private\n     * @param {float} price\n     * @param {integer} precision\n     * @returns {string}\n     */\n    _priceToStr: function (price, precision) {\n        if (!Number.isInteger(precision)) {\n            precision = parseInt(\n                this.el.querySelector('.decimal_precision:last-of-type')?.dataset.precision ?? 2\n            );\n        }\n        const formatted = price.toFixed(precision).split('.');\n        const { thousandsSep, decimalPoint, grouping } = localization;\n        formatted[0] = insertThousandsSep(formatted[0], thousandsSep, grouping);\n        return formatted.join(decimalPoint);\n    },\n\n    /**\n     * Returns a throttled `_getCombinationInfo` with a leading and a trailing\n     * call, which is memoized per `uniqueId`, and for which previous results\n     * are dropped.\n     *\n     * The uniqueId is needed because on the configurator modal there might be\n     * multiple elements triggering the rpc at the same time, and we need each\n     * individual product rpc to be executed, but only once per individual\n     * product.\n     *\n     * The leading execution is to keep good reactivity on the first call, for\n     * a better user experience. The trailing is because ultimately only the\n     * information about the last selected combination is useful. All\n     * intermediary rpc can be ignored and are therefore best not done at all.\n     *\n     * The keepLast is to make sure we only consider the result of the last call, when several\n     * (asynchronous) calls are done in parallel.\n     *\n     * @private\n     * @param {string} uniqueId\n     * @returns {function}\n     */\n    _throttledGetCombinationInfo: memoize(function (self, uniqueId) {\n        const keepLast = new KeepLast();\n        const _getCombinationInfo = throttleForAnimation(self._getCombinationInfo.bind(self));\n        return (ev, params) => keepLast.add(_getCombinationInfo(ev, params));\n    }),\n};\n\nexport default VariantMixin;\n", "import { markup } from '@odoo/owl';\nimport { browser } from '@web/core/browser/browser';\nimport { _t } from '@web/core/l10n/translation';\nimport { setElementContent } from '@web/core/utils/html';\n\nfunction animateClone($cart, $elem, offsetTop, offsetLeft) {\n    if (!$cart.length) {\n        return Promise.resolve();\n    }\n    $cart.removeClass('d-none').find('.o_animate_blink').addClass('o_red_highlight o_shadow_animation').delay(500).queue(function () {\n        $(this).removeClass(\"o_shadow_animation\").dequeue();\n    }).delay(2000).queue(function () {\n        $(this).removeClass(\"o_red_highlight\").dequeue();\n    });\n    return new Promise(function (resolve, reject) {\n        if(!$elem) resolve();\n        var $imgtodrag = $elem.find('img').eq(0);\n        if ($imgtodrag.length) {\n            var $imgclone = $imgtodrag.clone()\n                .offset({\n                    top: $imgtodrag.offset().top,\n                    left: $imgtodrag.offset().left\n                })\n                .removeClass()\n                .addClass('o_website_sale_animate')\n                .appendTo(document.body)\n                .css({\n                    // Keep the same size on cloned img.\n                    width: $imgtodrag.width(),\n                    height: $imgtodrag.height(),\n                })\n                .animate({\n                    top: $cart.offset().top + offsetTop,\n                    left: $cart.offset().left + offsetLeft,\n                    width: 75,\n                    height: 75,\n                }, 500);\n\n            $imgclone.animate({\n                width: 0,\n                height: 0,\n            }, function () {\n                resolve();\n                $(this).detach();\n            });\n        } else {\n            resolve();\n        }\n    });\n}\n\n/**\n * Returns the closest product form to a given element if exists.\n * Required for product pages with full-width or no images where the \"Add to cart\" button can be \n * outside of the form.\n *\n * @param { HTMLElement } element - Reference to an HTML element in the DOM.\n * @returns { HTMLFormElement|undefined }\n */\nfunction getClosestProductForm(element){\n    return element.closest('form') ?? element.closest('.js_product')?.querySelector('form');\n}\n\n/**\n * Updates both navbar cart\n * @param {Object} data\n * @return {void}\n */\nfunction updateCartNavBar(data) {\n    browser.sessionStorage.setItem('website_sale_cart_quantity', data.cart_quantity);\n    // Mobile and Desktop elements have to be updated.\n    const cartQuantityElements = document.querySelectorAll('.my_cart_quantity');\n    for(const cartQuantityElement of cartQuantityElements) {\n        if (data.cart_quantity === 0) {\n            cartQuantityElement.classList.add('d-none');\n        } else {\n            const cartIconElement = document.querySelector('li.o_wsale_my_cart');\n            cartIconElement.classList.remove('d-none');\n            cartQuantityElement.classList.remove('d-none');\n            cartQuantityElement.classList.add('o_mycart_zoom_animation');\n            setTimeout(() => {\n                cartQuantityElement.textContent = data.cart_quantity;\n                cartQuantityElement.classList.remove('o_mycart_zoom_animation');\n            }, 300);\n        }\n    }\n\n    $(\".js_cart_lines\").first().before(data['website_sale.cart_lines']).end().remove();\n\n    updateCartSummary(data);\n\n    // Adjust the cart's left column width to accommodate the cart summary (right column). The left\n    // column of an empty cart initially takes the full width, but adding products (e.g. via quick \n    // reorder) enables the cart summary on the right.\n    document.querySelector('.oe_cart').classList.toggle('col-lg-7', !!data.cart_quantity);\n\n    if (data.cart_ready) {\n        document.querySelector(\"a[name='website_sale_main_button']\")?.classList.remove('disabled');\n    } else {\n        document.querySelector(\"a[name='website_sale_main_button']\")?.classList.add('disabled');\n    }\n}\n\n/**\n * Update the cart summary.\n *\n * @param {Object} data\n * @return {void}\n */\nfunction updateCartSummary(data) {\n    if (data['website_sale.shorter_cart_summary']) {\n        const shorterCartSummaryEl = document.querySelector('.o_wsale_shorter_cart_summary');\n        setElementContent(shorterCartSummaryEl, markup(data['website_sale.shorter_cart_summary']));\n    }\n    if (data['website_sale.total']) {\n        document.querySelectorAll('div.o_cart_total').forEach(\n            div => div.innerHTML = data['website_sale.total']\n        );\n    }\n}\n\n/**\n * Update the quick reorder side panel.\n *\n * @param {Object} data\n * @return {void}\n */\nfunction updateQuickReorderSidebar(data) {\n    const quickReorderButton  = document.getElementById('quick_reorder_button');\n    document.querySelectorAll('.o_wsale_quick_reorder_line_group').forEach(el => el.remove());\n    if (data['website_sale.quick_reorder_history'].trim()) {\n        document.querySelector('#quick_reorder_sidebar .offcanvas-body').insertAdjacentHTML(\n            'afterbegin', data['website_sale.quick_reorder_history']\n        );\n        quickReorderButton.removeAttribute('disabled');\n        quickReorderButton.parentElement.title = \"\";\n    } else {\n        quickReorderButton.click();\n        quickReorderButton.setAttribute('disabled', 'true');\n        quickReorderButton.parentElement.title = _t(\"No previous products available for reorder.\");\n    }\n}\n\n/**\n * Displays `message` in an alert box at the top of the page if it's a\n * non-empty string.\n *\n * @param {string | null} message\n */\nfunction showWarning(message) {\n    if (!message) {\n        return;\n    }\n    var $page = $('.oe_website_sale');\n    var cart_alert = $page.children('#data_warning');\n    if (!cart_alert.length) {\n        cart_alert = $(\n            '<div class=\"alert alert-danger alert-dismissible\" role=\"alert\" id=\"data_warning\">' +\n                '<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button> ' +\n                '<span></span>' +\n            '</div>').prependTo($page);\n    }\n    cart_alert.children('span:last-child').text(message);\n}\n\n/**\n * Return the selected attribute values from the given container.\n *\n * @param {Element} container the container to look into\n */\nfunction getSelectedAttributeValues(container) {\n    return Array.from(container.querySelectorAll(\n        'input.js_variant_change:checked, select.js_variant_change'\n    )).map(el => parseInt(el.value));\n}\n\nexport default {\n    animateClone: animateClone,\n    getClosestProductForm: getClosestProductForm,\n    updateCartNavBar: updateCartNavBar,\n    showWarning: showWarning,\n    getSelectedAttributeValues: getSelectedAttributeValues,\n    updateQuickReorderSidebar: updateQuickReorderSidebar,\n};\n", "/** @odoo-module **/\n/**\n * This code has been more that widely inspired by the multirange library\n * which can be found on https://github.com/LeaVerou/multirange.\n *\n * The license file can be found in the same folder as this file.\n */\n\n/**\n * The multirange library will display the two values as one range input with\n * two cursors linked by a background. This is to be used with Bootstrap\n * custom-range inputs.\n *\n * There is 2 number inputs on the right and left of the multirange to\n * display and allow quick and precise value modifications. They are\n * initialized with the same value provided to the input (min, max, step).\n *\n * There is 2 events that are added to the input:\n * - oldRangeValue: Triggered when the user clicks on a cursor or on focus\n *                  of the right or left number input.\n * - newRangeValue: Triggered when the user release a cursor or on focus\n *                  out of the right or left number input.\n *\n * The options available for the multirange are :\n * - On range input or as multirange method options:\n *     - min: minimal value of the range. Default: 0.\n *     - max: maximal value of the range. Default: 100.\n *     - step: precision of the range. Default: 1.\n *     - currency: symbol preceding the displayed values. Default: Empty.\n *     - currencyPosition: currency before/after value. Default: \"after\".\n *     - value: the current value of the range. Default: \"0,100\".\n *\n * - As multirange method options only:\n *     - displayCounterInput: if we display the value. Default: true.\n *\n * Initialization of a multiple range input can be done in two ways:\n *\n * Having the inputs with the options as properties and the multiple\n * property set will let the library initialize it just after DOM loaded.\n *\n * <input type=\"range\" multiple=\"multiple\" class=\"custom-range\n * range-with-input\" min=2 max=10 step=0.5 data-currency=\"\u20ac\"\n * data-currency-position=\"before\" value=\"4,8\"/>\n *\n * Providing a HTMLElement and an Object with the desired options.\n *\n * <input id=\"multi\" type=\"range\" class=\"custom-range\"/>\n *\n * multirange(document.querySelector('#multi'), {\n *     min: 2,\n *     max: 10,\n *     step: 0.5,\n *     currency: \"\u20ac\",\n *     currencyPosition: \"before\",\n *     value: \"4,8\"\n *     rangeWithInput: true,\n * });\n */\n\nconst HTMLInputElement = window.HTMLInputElement;\nconst descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, \"value\");\n\nexport class Multirange {\n    constructor(input, options = {}) {\n        const self = this;\n\n        /* Set default and optionnal values */\n        this.input = input;\n        this.rangeWithInput = options.rangeWithInput === true || this.input.classList.contains('range-with-input');\n        const value = options.value || this.input.getAttribute(\"value\");\n        const values = value === null ? [] : value.split(\",\");\n        this.input.min = this.min = options.min || this.input.min || 0;\n        this.input.max = this.max = options.max || this.input.max || 100;\n        this.input.step = this.step = options.step || this.input.step || 1;\n        this.currency = options.currency || this.input.dataset.currency || '';\n        this.currencyPosition = options.currencyPosition || this.input.dataset.currencyPosition || 'after';\n        const inputClasses = [\"multirange\", \"d-inline-block\", \"p-0\", \"align-top\"];\n\n        /* Wrap the input and add its ghost */\n        this.rangeDiv = document.createElement(\"div\");\n        this.rangeDiv.classList.add(\"multirange-wrapper\", \"position-relative\", \"mb-5\");\n        this.countersWrapper = document.createElement(\"small\");\n        this.countersWrapper.classList.add(\"d-flex\", \"justify-content-between\", \"mt-2\");\n        this.rangeDiv.appendChild(this.countersWrapper);\n        this.input.parentNode.insertBefore(this.rangeDiv, this.input.nextSibling);\n        this.rangeDiv.appendChild(this.input);\n        this.ghost = this.input.cloneNode();\n        this.rangeDiv.appendChild(this.ghost);\n\n        this.input.classList.add(\"original\", \"position-absolute\", \"w-100\", \"m-0\", ...inputClasses);\n        this.ghost.classList.add(\"ghost\", \"position-relative\", ...inputClasses);\n        this.input.value = values[0] || this.min;\n        this.ghost.value = values[1] || this.max;\n\n        this.leftCounter = document.createElement(\"span\");\n        this.leftCounter.classList.add(\"multirange-min\", \"position-absolute\", \"opacity-75\", \"opacity-100-hover\", \"mt-1\");\n        this.rightCounter = document.createElement(\"span\");\n        this.rightCounter.classList.add(\"multirange-max\", \"position-absolute\", \"opacity-75\", \"opacity-100-hover\", \"mt-1\", \"end-0\");\n        this.countersWrapper.append(this.leftCounter, this.rightCounter);\n\n        /* Add the counterInput */\n        if (this.rangeWithInput) {\n            this.leftInput = document.createElement(\"input\");\n            this.leftInput.type = \"number\";\n            this.leftInput.classList.add(\"invisible\", \"form-control\", \"form-control-sm\", \"mb-2\", \"mb-lg-1\");\n            this.leftInput.min = this.min;\n            this.leftInput.max = this.max;\n            this.leftInput.step = this.step;\n            this.rightInput = this.leftInput.cloneNode();\n\n            this.leftInput.classList.add(\"multirange-min\", \"invisible\");\n            this.rightInput.classList.add(\"multirange-max\", \"invisible\");\n\n            this.leftCounter.parentNode.appendChild(this.leftInput);\n            this.rightCounter.parentNode.appendChild(this.rightInput);\n        }\n\n        /* Define new properties on range input to link it with ghost, especially for Safari compatibility*/\n        Object.defineProperty(this.input, \"originalValue\", descriptor.get ? descriptor : {\n            get: function () {\n                return this.value;\n            },\n            set: function (v) {\n                this.value = v;\n            }\n        });\n\n        Object.defineProperties(this.input, {\n            valueLow: {\n                get: function () {\n                    return Math.min(this.originalValue, self.ghost.value);\n                },\n                set: function (v) {\n                    this.originalValue = v;\n                },\n                enumerable: true\n            },\n            valueHigh: {\n                get: function () {\n                    return Math.max(this.originalValue, self.ghost.value);\n                },\n                set: function (v) {\n                    self.ghost.value = v;\n                },\n                enumerable: true\n            }\n        });\n\n        if (descriptor.get) {\n            Object.defineProperty(this.input, \"value\", {\n                get: function () {\n                    return this.valueLow + \",\" + this.valueHigh;\n                },\n                set: function (v) {\n                    const values = v.split(\",\");\n                    this.valueLow = values[0];\n                    this.valueHigh = values[1];\n                    this.update();\n                },\n                enumerable: true\n            });\n        }\n\n        if (typeof this.input.oninput === \"function\") {\n            this.ghost.oninput = this.input.oninput.bind(this.input);\n        }\n\n        this.input.addEventListener(\"input\", this.update.bind(this));\n        this.ghost.addEventListener(\"input\", this.update.bind(this));\n\n        this.input.addEventListener(\"touchstart\", this.saveOldValues.bind(this));\n        this.ghost.addEventListener(\"touchstart\", this.saveOldValues.bind(this));\n        this.input.addEventListener(\"mousedown\", this.saveOldValues.bind(this));\n        this.ghost.addEventListener(\"mousedown\", this.saveOldValues.bind(this));\n\n        this.input.addEventListener(\"touchend\", this.dispatchNewValueEvent.bind(this));\n        this.ghost.addEventListener(\"touchend\", this.dispatchNewValueEvent.bind(this));\n        this.input.addEventListener(\"mouseup\", this.dispatchNewValueEvent.bind(this));\n        this.ghost.addEventListener(\"mouseup\", this.dispatchNewValueEvent.bind(this));\n\n        if (this.rangeWithInput) {\n            this.leftCounter.addEventListener(\"click\", this.counterInputSwitch.bind(this));\n            this.rightCounter.addEventListener(\"click\", this.counterInputSwitch.bind(this));\n\n            this.leftInput.addEventListener(\"blur\", this.counterInputSwitch.bind(this));\n            this.rightInput.addEventListener(\"blur\", this.counterInputSwitch.bind(this));\n\n            this.leftInput.addEventListener(\"keypress\", this.elementBlurOnEnter.bind(this));\n            this.rightInput.addEventListener(\"keypress\", this.elementBlurOnEnter.bind(this));\n\n            this.leftInput.addEventListener(\"focus\", this.selectAllFocus.bind(this));\n            this.rightInput.addEventListener(\"focus\", this.selectAllFocus.bind(this));\n        }\n        this.update();\n        // Single change from original lib: removed jQ from next line:\n        this.rangeDiv.classList.add('visible');\n    }\n\n    update() {\n        const low = 100 * (this.input.valueLow - this.min) / (this.max - this.min);\n        const high = 100 * (this.input.valueHigh - this.min) / (this.max - this.min);\n\n        this.rangeDiv.style.setProperty(\"--low\", low + '%');\n        this.rangeDiv.style.setProperty(\"--high\", high + '%');\n        this.counterInputUpdate();\n    }\n\n    counterInputUpdate() {\n        if (this.rangeWithInput) {\n            this.leftCounter.innerText = this.formatNumber(this.input.valueLow);\n            this.rightCounter.innerText = this.formatNumber(this.input.valueHigh);\n            this.leftInput.value = this.input.valueLow;\n            this.rightInput.value = this.input.valueHigh;\n        }\n    }\n\n    counterInputSwitch(ev) {\n        let counter = this.rightCounter;\n        let input = this.rightInput;\n        if (ev.currentTarget.classList.contains('multirange-min')) {\n            counter = this.leftCounter;\n            input = this.leftInput;\n        }\n\n        if (counter.classList.contains(\"invisible\")) {\n            this.input.valueLow = this.leftInput.value;\n            this.input.valueHigh = this.rightInput.value;\n            this.dispatchNewValueEvent();\n            this.update();\n            counter.classList.remove(\"invisible\");\n            input.classList.add(\"invisible\");\n        } else {\n            counter.classList.add(\"invisible\");\n            input.classList.remove(\"invisible\");\n            this.saveOldValues();\n            // Hack because firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1057858\n            window.setTimeout(function () {\n                input.focus();\n            }, 1);\n        }\n    }\n\n    elementBlurOnEnter(ev) {\n        if (ev.key === \"Enter\") {\n            ev.currentTarget.blur();\n        }\n    }\n\n    selectAllFocus(ev) {\n        ev.currentTarget.select();\n    }\n\n    dispatchNewValueEvent() {\n        if (this._previousMaxPrice !== this.input.valueHigh || this._previousMinPrice !== this.input.valueLow) {\n            this.input.dispatchEvent(new CustomEvent(\"newRangeValue\", {\n                bubbles: true,\n            }));\n        }\n    }\n\n    saveOldValues() {\n        this._previousMinPrice = this.input.valueLow;\n        this._previousMaxPrice = this.input.valueHigh;\n    }\n\n    formatNumber(number) {\n        const language = document.querySelector(\"html\").getAttribute(\"lang\");\n        const locale = (() => {\n            switch (language) {\n                case \"sr@latin\":\n                    return \"sr-Latn\";\n                case \"sr@Cyrl\":\n                    return \"sr-Cyrl\";\n                default:\n                    return language.replaceAll(\"_\", \"-\");\n            }\n        })();\n        let formatedNumber = number.toLocaleString(locale, {\n            minimumFractionDigits: 2,\n            maximumFractionDigits: 2,\n        });\n        if (this.currency.length) {\n            if (this.currencyPosition === 'after') {\n                formatedNumber = formatedNumber + ' ' + this.currency;\n            } else {\n                formatedNumber = this.currency + ' ' + formatedNumber;\n            }\n        }\n        return formatedNumber;\n    }\n}\n\nexport function multirange(input, options) {\n    if (input.classList.contains('multirange')) {\n        return;\n    }\n    new Multirange(input, options);\n}\n\nexport default {\n    Multirange: Multirange,\n    init: multirange,\n};\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport multirange from \"@website/../lib/multirange/multirange_custom\";\n\nexport class MultirangeInput extends Interaction {\n    static selector = \"input[type=range][multiple]:not(.multirange)\";\n\n    start() {\n        multirange.init(this.el);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.multirange_input\", MultirangeInput);\n", "import { onMounted, onRendered, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\n\nconst ZOOM_STEP = 0.1;\nconst TOUCHMOVE_STEP = 96;\n\nexport class ProductImageViewer extends Dialog {\n    static template = \"website_sale.ProductImageViewer\";\n    static props = {\n        ...Dialog.props,\n        images: { type: NodeList, required: true },\n        selectedImageIdx: { type: Number, optional: true },\n        imageRatio: { type: String, optional: true },\n        close: Function,\n    };\n\n    setup() {\n        super.setup();\n        this.imageContainerRef = useRef(\"imageContainer\");\n        this.images = [...this.props.images].map(image => {\n            return {\n                src: image.dataset.zoomImage || image.src,\n                thumbnailSrc: image.src.replace('/image_1024/', '/image_256/'),\n            };\n        });\n        this.state = useState({\n            selectedImageIdx: this.props.selectedImageIdx || 0,\n            imageScale: 1,\n            carouselOffset: 0,\n        });\n        this.isDragging = false;\n        this.dragStartPos = { x: 0, y: 0 };\n        // Doing a full render for the translate is too slow.\n        this.imageTranslate = { x: 0, y: 0 };\n        useHotkey(\"arrowleft\", this.previousImage.bind(this));\n        useHotkey(\"arrowright\", this.nextImage.bind(this));\n        useHotkey(\"r\", () => {\n            this.imageTranslate = { x: 0, y: 0 };\n            this.isDragging = false;\n            this.state.imageScale = 1;\n            this.updateImage();\n        });\n\n        // Not using a t-on-click on purpose because we want to be able to cancel the drag\n        // when we go outside of the window.\n        useEffect(\n            (document) => {\n                const onGlobalClick = this.onGlobalClick.bind(this);\n                document.addEventListener(\"click\", onGlobalClick);\n                return () => {document.removeEventListener(\"click\", onGlobalClick)};\n            },\n            () => [document],\n        );\n        onMounted(() => {\n            const carousel = document.querySelector('.o_wsale_image_viewer_carousel');\n            if (carousel) {\n                carousel.addEventListener('touchstart', this._onTouchstartCarousel.bind(this));\n                carousel.addEventListener('touchmove', this._onTouchmoveCarousel.bind(this));\n                const lastImg = carousel.querySelector('li:last-of-type img');\n                lastImg?.addEventListener('load', this._updateCarousel.bind(this), { once: true });\n            }\n        });\n        // For some reason the styling does not always update properly.\n        onRendered(() => {\n            this.updateImage();\n        })\n    }\n\n    get selectedImage() {\n        return this.images[this.state.selectedImageIdx];\n    }\n\n    set selectedImage(image) {\n        this.state.imageScale = 1;\n        this.imageTranslate = { x: 0, y: 0 };\n        this.state.selectedImageIdx = this.images.indexOf(image);\n        this._updateCarousel();\n    }\n\n    get imageStyle() {\n        return `transform:\n            scale3d(${this.state.imageScale}, ${this.state.imageScale}, 1);\n        `;\n    }\n\n    get imageContainerStyle() {\n        return `transform: translate(${this.imageTranslate.x}px, ${this.imageTranslate.y}px);`;\n    }\n\n    previousImage() {\n        this.selectedImage = this.images[(this.state.selectedImageIdx - 1 + this.images.length) % this.images.length];\n    }\n\n    nextImage() {\n        this.selectedImage = this.images[(this.state.selectedImageIdx + 1) % this.images.length];\n    }\n\n    updateImage() {\n        if (!this.imageContainerRef || !this.imageContainerRef.el) {\n            return;\n        }\n        this.imageContainerRef.el.style = this.imageContainerStyle;\n    }\n\n    /**\n     * Centers the thumbnail row element on the currently selected image.\n     *\n     * @private\n     */\n    _updateCarousel() {\n        const thumbnailList = document.querySelector('.o_wsale_image_viewer_carousel ol');\n        const viewWidth = window.visualViewport.width;\n        if (!thumbnailList || thumbnailList.scrollWidth <= viewWidth) {\n            return;\n        }\n        const { selectedImageIdx } = this.state;\n        const thumbnail = thumbnailList.childNodes[selectedImageIdx];\n        const { left: thumbOffset, width: thumbWidth } = thumbnail.getBoundingClientRect();\n\n        this.state.carouselOffset += (viewWidth - thumbWidth) / 2 - thumbOffset;\n        thumbnailList.style.transform = `translate(${this.state.carouselOffset}px)`;\n    }\n\n    onGlobalClick(ev) {\n        if (ev.target.tagName === \"IMG\") {\n            // Only zoom if the image did not move\n            if (this.dragStartPos.clientX === ev.clientX && this.dragStartPos.clientY === ev.clientY) {\n                if (this.state.imageScale <= 1) {\n                    this.zoomIn(ZOOM_STEP * 3);\n                } else {\n                    this.zoomOut(this.state.imageScale - 1);\n                }\n            }\n        }\n        if (ev.target.classList.contains('o_wsale_image_viewer_void') && !this.isDragging) {\n            ev.stopPropagation();\n            ev.preventDefault();\n            this.data.close();\n        } else {\n            this.isDragging = false;\n        }\n    }\n\n    zoomIn(step=undefined) {\n        this.state.imageScale += step || ZOOM_STEP;\n    }\n\n    zoomOut(step=undefined) {\n        this.state.imageScale = Math.max(0.5, this.state.imageScale - (step || ZOOM_STEP));\n    }\n\n    onWheelImage(ev) {\n        if (!ev.deltaY) {\n            return;\n        }\n        ev.preventDefault();\n        if (ev.deltaY > 0) {\n            this.zoomOut();\n        } else {\n            this.zoomIn();\n        }\n    }\n\n    onMousedownImage(ev) {\n        this.isDragging = true;\n        this.dragStartPos = {\n            x: ev.clientX - this.imageTranslate.x,\n            y: ev.clientY - this.imageTranslate.y,\n            clientX: ev.clientX,\n            clientY: ev.clientY,\n        };\n    }\n\n    onGlobalMousemove(ev) {\n        if (!this.isDragging) {\n            return;\n        }\n        this.imageTranslate.x = ev.clientX - this.dragStartPos.x;\n        this.imageTranslate.y = ev.clientY - this.dragStartPos.y;\n        this.updateImage();\n    }\n\n    _onTouchstartCarousel(ev) {\n        const touch = ev.touches?.item(0);\n        if (!touch) {\n            return;\n        }\n        this.state.touchClientX = touch.clientX;\n        if (!this.state.touchmoveStep) {\n            const thumbnail = document.querySelector('img.o_wsale_image_viewer_thumbnail');\n            this.state.touchmoveStep = 0.75 * thumbnail?.clientWidth;\n        }\n    }\n\n    _onTouchmoveCarousel(ev) {\n        const touch = ev.touches?.item(0);\n        if (!touch) {\n            return;\n        }\n        ev.preventDefault();\n        const { selectedImageIdx, touchmoveStep, touchClientX } = this.state;\n        const deltaX = touch.clientX - touchClientX;\n        const step = touchmoveStep || TOUCHMOVE_STEP;\n        if (deltaX > step && selectedImageIdx > 0) {\n            this.state.touchClientX += step;\n            this.previousImage();\n        } else if (deltaX < -step && selectedImageIdx < this.images.length - 1) {\n            this.state.touchClientX -= step;\n            this.nextImage();\n        }\n    }\n}\ndelete ProductImageViewer.props.slots;\n", "import { Component } from \"@odoo/owl\";\nimport { formatCurrency } from \"@web/core/currency\";\n\nexport class AddToCartNotification extends Component {\n    static template = \"website_sale.addToCartNotification\";\n    static props = {\n        lines: {\n            type: Array,\n            element: {\n                type: Object,\n                shape: {\n                    id: Number,\n                    linked_line_id: { type: Number, optional: true },\n                    image_url: String,\n                    quantity: Number,\n                    uom_name: { type: String, optional: true },\n                    combination_name: { type: String, optional: true },\n                    name: String,\n                    description: { type: String, optional: true },\n                    price_total: Number,\n                },\n            },\n        },\n        currency_id: Number,\n    }\n\n    /**\n     * Return the lines which aren't linked to other lines.\n     *\n     * @return {Object[]} The lines which aren't linked to other lines.\n     */\n    get mainLines() {\n        return this.props.lines.filter(line => !line.linked_line_id);\n    }\n\n    /**\n     * Return the lines linked to the provided line id.\n     *\n     * @param {Number} lineId The id of the line whose linked lines to return.\n     * @return {Object[]} The lines which aren't linked to other lines.\n     */\n    getLinkedLines(lineId) {\n        return this.props.lines.filter(line => line.linked_line_id === lineId);\n    }\n\n    /**\n     * Return the price, in the format of the sale order currency.\n     *\n     * @param {Object} line - The line element for which to return the formatted price.\n     * @return {String} - The price, in the format of the sale order currency.\n     */\n    getFormattedPrice(line) {\n        const linkedLines = this.getLinkedLines(line.id);\n        const price = linkedLines.length\n            ? linkedLines.reduce((price, linkedLine) => price + linkedLine.price_total, 0)\n            : line.price_total;\n        return formatCurrency(price, this.props.currency_id);\n    }\n\n}\n", "import { Component, onMounted } from \"@odoo/owl\";\nimport { AddToCartNotification } from \"../add_to_cart_notification/add_to_cart_notification\";\nimport { WarningNotification } from \"../warning_notification/warning_notification\";\n\nconst AUTOCLOSE_DELAY = 4000;\n\nexport class CartNotification extends Component {\n    static components = { AddToCartNotification, WarningNotification };\n    static template = \"website_sale.cartNotification\";\n    static props = {\n        message: [String, { toString: Function }],\n        warning: {type : [String, { toString: Function }], optional: true},\n        lines: {\n            type: Array,\n            optional: true,\n            element: {\n                type: Object,\n                shape: {\n                    id: Number,\n                    linked_line_id: { type: Number, optional: true },\n                    image_url: String,\n                    quantity: Number,\n                    uom_name: { type: String, optional: true },\n                    name: String,\n                    combination_name: { type: String, optional: true },\n                    description: { type: String, optional: true },\n                    price_total: Number,\n                },\n            },\n        },\n        currency_id: {type: Number, optional: true},\n        className: String,\n        close: Function,\n    }\n\n    setup() {\n        onMounted(() => setTimeout(this.props.close, AUTOCLOSE_DELAY));\n    }\n\n    /**\n     * Get the top position (in px) of the notification based on the navbar height.\n     *\n     * This prevents the notification from being shown in front of the navbar.\n     */\n    get positionOffset() {\n        return (document.querySelector('header.o_top_fixed_element')?.offsetHeight || 0) + 'px';\n    }\n}\n", "import { Component } from \"@odoo/owl\";\n\nexport class WarningNotification extends Component {\n    static template = \"website_sale.warningNotification\";\n    static props = {\n        warning: [String, { toString: Function }],\n    }\n}\n", "import { xml } from \"@odoo/owl\";\nimport { NotificationContainer } from \"@web/core/notifications/notification_container\";\nimport { notificationService } from \"@web/core/notifications/notification_service\";\nimport { registry } from \"@web/core/registry\";\nimport { CartNotification } from \"@website_sale/js/notification/cart_notification/cart_notification\";\n\n\nexport class CartNotificationContainer extends NotificationContainer {\n    static components = {\n        ...NotificationContainer.components,\n        Notification: CartNotification,\n    }\n    static template = xml`\n    <div class=\"position-fixed w-100 h-100 top-0 pe-none\">\n        <div class=\"d-flex flex-column container align-items-end\">\n            <t t-foreach=\"notifications\" t-as=\"notification\" t-key=\"notification\">\n                <Transition leaveDuration=\"0\" name=\"'o_notification_fade'\" t-slot-scope=\"transition\">\n                    <Notification t-props=\"notification_value.props\" className=\"(notification_value.props.className || '') + ' ' + transition.className\"/>\n                </Transition>\n            </t>\n        </div>\n    </div>`;\n}\n\nexport const cartNotificationService = {\n    ...notificationService,\n    notificationContainer: CartNotificationContainer,\n}\n\nregistry.category(\"services\").add(\"cartNotificationService\", cartNotificationService);\n", "import { Component } from \"@odoo/owl\";\nimport { formatCurrency } from \"@web/core/currency\";\n\nexport class BadgeExtraPrice extends Component {\n    static template = \"sale.BadgeExtraPrice\";\n    static props = {\n        price: Number,\n        currencyId: Number,\n    };\n\n    /**\n     * Return the price, in the format of the given currency.\n     *\n     * @return {String} - The price, in the format of the given currency.\n     */\n    getFormattedPrice() {\n        return formatCurrency( Math.abs(this.props.price), this.props.currencyId);\n    }\n}\n", "import { Component, useState, useSubEnv } from '@odoo/owl';\nimport { formatCurrency } from '@web/core/currency';\nimport { Dialog } from '@web/core/dialog/dialog';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport { useService } from '@web/core/utils/hooks';\nimport { ProductCombo } from '../models/product_combo';\nimport { ProductTemplateAttributeLine } from '../models/product_template_attribute_line';\nimport { ProductCard } from '../product_card/product_card';\nimport {\n    ProductConfiguratorDialog\n} from '../product_configurator_dialog/product_configurator_dialog';\nimport { QuantityButtons } from '../quantity_buttons/quantity_buttons';\n\nexport class ComboConfiguratorDialog extends Component {\n    static template = 'sale.ComboConfiguratorDialog';\n    static components = { Dialog, ProductCard, QuantityButtons };\n    static props = {\n        product_tmpl_id: Number,\n        display_name: String,\n        quantity: Number,\n        price: Number,\n        combos: { type: Array, element: ProductCombo },\n        currency_id: Number,\n        company_id: { type: Number, optional: true },\n        pricelist_id: { type: Number, optional: true },\n        date: String,\n        price_info: { type: String, optional: true },\n        edit: { type: Boolean, optional: true },\n        options: {\n            type: Object,\n            optional: true,\n            shape: {\n                showQuantity : { type: Boolean, optional: true },\n                showPrice : { type: Boolean, optional: true },\n            },\n        },\n        save: Function,\n        discard: Function,\n        close: Function,\n    };\n\n    setup() {\n        this.dialog = useService('dialog');\n        this.env.dialogData.dismiss = !this.props.edit && this.props.discard.bind(this);\n        this.state = useState({\n            // Maps combo ids to selected combo items.\n            // Note that selected combo items can be modified (i.e. their `no_variant` PTAVs can be\n            // updated), so this map stores deep copies to avoid modifying the props.\n            selectedComboItems: new Map(),\n            quantity: this.props.quantity,\n            basePrice: this.props.price,\n            isLoading: false,\n        });\n        this._initSelectedComboItems();\n        this.getPriceUrl = '/sale/combo_configurator/get_price';\n        useSubEnv({ currency: { id: this.props.currency_id } });\n\n        this.unconfigurableCombos = this.props.combos.filter(combo => !combo.isConfigurable);\n        this.configurableCombos = this.props.combos.filter(combo => combo.isConfigurable);\n    }\n\n    /**\n     * Select the provided combo item, and open the product configurator iff the combo item's\n     * product is configurable.\n     *\n     * @param {Number} comboId The id of the combo to which the combo item belongs.\n     * @param {ProductComboItem} comboItem The combo item to select.\n     */\n    async selectComboItem(comboId, comboItem) {\n        // Use up-to-date selected PTAVs and custom values to populate the product configurator.\n        comboItem = this.getSelectedOrProvidedComboItem(comboId, comboItem);\n        let product = comboItem.product;\n        if (comboItem.is_configurable) {\n            this.dialog.add(ProductConfiguratorDialog, {\n                productTemplateId: product.product_tmpl_id,\n                ptavIds: product.selectedPtavIds,\n                customPtavs: product.selectedCustomPtavs,\n                quantity: 1,\n                companyId: this.props.company_id,\n                pricelistId: this.props.pricelist_id,\n                currencyId: this.props.currency_id,\n                soDate: this.props.date,\n                edit: true, // Hide the optional products, if any.\n                options: {\n                    canChangeVariant: false,\n                    showQuantity: false,\n                    showPrice: false,\n                    showPackaging: false,\n                },\n                size: \"md\",\n                save: async configuredProduct => {\n                    const selectedComboItem = comboItem.deepCopy();\n                    selectedComboItem.product.ptals = configuredProduct.attribute_lines.map(\n                        ProductTemplateAttributeLine.fromProductConfiguratorPtal\n                    );\n                    this.state.selectedComboItems.set(comboId, selectedComboItem);\n                },\n                discard: () => {},\n                ...this._getAdditionalDialogProps(),\n            });\n        } else {\n            this.state.selectedComboItems.set(comboId, comboItem.deepCopy());\n        }\n    }\n\n    /**\n     * Sets the quantity of this combo product.\n     *\n     * @param {Number} quantity The new quantity of this combo product.\n     */\n    async setQuantity(quantity) {\n        if (quantity <= 0) quantity = 1;\n        this.state.quantity = quantity;\n        this.state.basePrice = await rpc(this.getPriceUrl, {\n            product_tmpl_id: this.props.product_tmpl_id,\n            currency_id: this.props.currency_id,\n            quantity: quantity,\n            date: this.props.date,\n            company_id: this.props.company_id,\n            pricelist_id: this.props.pricelist_id,\n            ...this._getAdditionalRpcParams(),\n        });\n    }\n\n    /**\n     * Return the selected or provided combo item.\n     *\n     * If the provided combo item was already selected, then it may contain stale data (i.e.\n     * selected PTAVs, custom values), and we should rely on the data in `state.selectedComboItems`\n     * instead. Otherwise, the data in the provided combo item is up-to-date and can be used.\n     *\n     * @param {Number} comboId The id of the combo to which the combo item belongs.\n     * @param {ProductComboItem} comboItem The provided combo item.\n     * @return {ProductComboItem} The selected or provided combo item.\n     */\n    getSelectedOrProvidedComboItem(comboId, comboItem) {\n        const selectedComboItem = this.state.selectedComboItems.get(comboId);\n        const isComboItemAlreadySelected = selectedComboItem?.id === comboItem.id;\n        return isComboItemAlreadySelected ? selectedComboItem : comboItem;\n    }\n\n    get totalMessage() {\n        return _t(\"Total: %s\", this.formattedTotalPrice);\n    }\n\n    /**\n     * Return the total price for all units, formatted using the provided currency.\n     *\n     * @return {String} The formatted total price.\n     */\n    get formattedTotalPrice() {\n        return formatCurrency(this.state.quantity * this._comboPrice, this.props.currency_id);\n    }\n\n    /**\n     * Check whether a combo item has been selected for each combo.\n     *\n     * @return {Boolean} Whether a combo item has been selected for each combo.\n     */\n    get areAllCombosSelected() {\n        return this.state.selectedComboItems.size === this.props.combos.length;\n    }\n\n    async confirm(options) {\n        this.state.isLoading = true;\n        await this.props.save(this._comboProductData, this._selectedComboItems, options).finally(\n            () => this.state.isLoading = false\n        )\n        this.props.close();\n    }\n\n    cancel() {\n        if (!this.props.edit) {\n            this.props.discard();\n        }\n        this.props.close();\n    }\n\n    /**\n     * Initialize the selected combo item in each combo.\n     */\n    _initSelectedComboItems() {\n        for (const combo of this.props.combos) {\n            const comboItem = combo.selectedComboItem;\n            if (comboItem) {\n                this.state.selectedComboItems.set(combo.id, comboItem.deepCopy());\n            }\n        }\n    }\n\n    /**\n     * Return the total price per unit.\n     *\n     * The total price is the sum of:\n     * - The combo product's price,\n     * - The selected combo items' extra price,\n     * - The selected `no_variant` attributes' extra price.\n     *\n     * @return {Number} The total price.\n     */\n    get _comboPrice() {\n        const extraPrice = Array.from(this.state.selectedComboItems.values()).reduce(\n            (price, item) => price + item.totalExtraPrice, 0\n        );\n        return this.state.basePrice + extraPrice;\n    }\n\n    /**\n     * Return data about the combo product.\n     *\n     * @return {Object} Data about the combo product.\n     */\n    get _comboProductData() {\n        return { 'quantity': this.state.quantity };\n    }\n\n    /**\n     * Return the selected combo items, in the same order as the combos given as props.\n     *\n     * @return {ProductComboItem[]} The sorted selected combo items.\n     */\n    get _selectedComboItems() {\n        const sortedItems = new Map([...this.state.selectedComboItems.entries()].sort(\n            (entry1, entry2) =>\n                this.props.combos.findIndex(combo => combo.id === entry1[0])\n                - this.props.combos.findIndex(combo => combo.id === entry2[0])\n        ));\n        return Array.from(sortedItems.values());\n    }\n\n    /**\n     * Hook to append additional RPC params in overriding modules.\n     *\n     * @return {Object} The additional RPC params.\n     */\n    _getAdditionalRpcParams() {\n        return {};\n    }\n\n    /**\n     * Hook to append additional props in overriding modules.\n     *\n     * @return {Object} The additional props.\n     */\n    _getAdditionalDialogProps() {\n        return {};\n    }\n}\n", "import { ProductComboItem } from './product_combo_item';\n\nexport class ProductCombo {\n    /**\n     * @param {number} id\n     * @param {string} name\n     * @param {ProductComboItem[]|object[]} combo_items\n     */\n    constructor({id, name, combo_items}) {\n        this.id = id;\n        this.name = name;\n        this.combo_items = combo_items.map(item => new ProductComboItem(item));\n    }\n\n    /**\n     * Return the selected combo item, if any.\n     *\n     * @return {ProductComboItem|undefined} The selected combo item, if any.\n     */\n    get selectedComboItem() {\n        return this.combo_items.find(item => item.is_selected);\n    }\n\n    /**\n    * Return the preselected combo item, if any.\n    *\n    * @return {ProductComboItem|undefined} The preselected combo items, if any.\n    */\n    get preselectedComboItem() {\n        return this.combo_items.find(item => item.is_preselected);\n    }\n\n    /**\n     * Check whether this combo is configurable.\n     *\n     * @return {Boolean} Whether this combo is configurable.\n     */\n    get isConfigurable() {\n        return !this.combo_items.some(item => item.is_preselected);\n    }\n}\n", "import { ProductProduct } from './product_product';\n\nexport class ProductComboItem {\n    /**\n     * @param {number} id\n     * @param {number} extra_price\n     * @param {boolean} is_preselected\n     * @param {boolean} is_selected\n     * @param {boolean} is_configurable\n     * @param {ProductProduct|object} product\n     */\n    constructor({id, extra_price, is_preselected, is_selected, is_configurable, product}) {\n        this.id = id;\n        this.extra_price = extra_price;\n        this.is_preselected = is_preselected;\n        this.is_selected = is_selected;\n        this.is_configurable = is_configurable;\n        this.product = new ProductProduct(product);\n    }\n\n    /**\n     * Return the combo item's \"total\" extra price.\n     *\n     * The total extra price is the sum of:\n     * - The combo item's extra price,\n     * - The extra price of the selected `no_variant` PTAVs of the combo item's product.\n     *\n     * @return {Number} The combo item's \"total\" extra price.\n     */\n    get totalExtraPrice() {\n        return this.extra_price + this.product.selectedNoVariantPtavsPriceExtra;\n    }\n\n    /**\n     * Return a deep copy of this combo item.\n     *\n     * @return {ProductComboItem} A deep copy of this combo item.\n     */\n    deepCopy() {\n        return new ProductComboItem(JSON.parse(JSON.stringify(this)));\n    }\n}\n", "import { ProductTemplateAttributeLine } from './product_template_attribute_line';\n\nexport class ProductProduct {\n    /**\n     * The instance is initialized in `setup` to allow patching, as constructors can't be patched.\n     */\n    constructor(...args) {\n        this.setup(...args);\n    }\n\n    /**\n     * @param {number} id\n     * @param {number} product_tmpl_id\n     * @param {string} display_name\n     * @param {ProductTemplateAttributeLine[]|object[]} ptals\n     * @param {string} image_src\n     * @param {string} description\n     */\n    setup({id, product_tmpl_id, display_name, ptals, image_src, description}) {\n        this.id = id;\n        this.product_tmpl_id = product_tmpl_id;\n        this.display_name = display_name;\n        this.ptals = ptals.map(ptal => new ProductTemplateAttributeLine(ptal));\n        this.image_src = image_src;\n        this.description = description;\n    }\n\n    /**\n     * Return the `no_variant` PTALs.\n     *\n     * @return {ProductTemplateAttributeLine[]} The `no_variant` PTALs.\n     */\n    get noVariantPtals() {\n        return this.ptals.filter(ptal => ptal.create_variant === 'no_variant');\n    }\n\n    /**\n     * Return the extra price of the selected `no_variant` PTAVs.\n     *\n     * @return {Number} The extra price of the selected `no_variant` PTAVs.\n     */\n    get selectedNoVariantPtavsPriceExtra() {\n        return this.noVariantPtals.reduce((price, ptal) => price + ptal.selectedPtavsPriceExtra, 0);\n    }\n\n    /**\n     * Return the selected PTAV ids.\n     *\n     * @return {Number[]} The selected PTAV ids.\n     */\n    get selectedPtavIds() {\n        return this.ptals.flatMap(ptal => ptal.selected_ptavs).map(ptav => ptav.id);\n    }\n\n    /**\n     * Return the selected `no_variant` PTAV ids.\n     *\n     * @return {Number[]} The selected `no_variant` PTAV ids.\n     */\n    get selectedNoVariantPtavIds() {\n        return this.noVariantPtals.flatMap(ptal => ptal.selected_ptavs).map(ptav => ptav.id);\n    }\n\n    /**\n     * Return the selected custom PTAVs.\n     *\n     * @return {{id: Number, value: String}[]} The selected custom PTAVs.\n     */\n    get selectedCustomPtavs() {\n        return this.ptals.filter(ptal => ptal.hasSelectedCustomPtav).flatMap(\n            ptal => ptal.selected_ptavs\n        ).map(ptav => ({\n            'id': ptav.id,\n            'value': ptav.custom_value,\n        }));\n    }\n}\n", "import { ProductTemplateAttributeValue } from './product_template_attribute_value';\n\nexport class ProductTemplateAttributeLine {\n    /**\n     * @param {number} id\n     * @param {string} name\n     * @param {'always'|'dynamic'|'no_variant'} create_variant\n     * @param {ProductTemplateAttributeValue[]|object[]} selected_ptavs\n     */\n    constructor({id, name, create_variant, selected_ptavs}) {\n        this.id = id;\n        this.name = name;\n        this.create_variant = create_variant;\n        this.selected_ptavs = selected_ptavs.map(ptav => new ProductTemplateAttributeValue(ptav));\n    }\n\n    /**\n     * Construct a ProductTemplateAttributeLine from the provided \"product configurator\"-shaped\n     * PTAL.\n     *\n     * @param productConfiguratorPtal The \"product configurator\"-shaped PTAL.\n     * @return {ProductTemplateAttributeLine} The corresponding ProductTemplateAttributeLine.\n     */\n    static fromProductConfiguratorPtal(productConfiguratorPtal) {\n        const selectedPtavIds = new Set(productConfiguratorPtal.selected_attribute_value_ids);\n        const selectedPtavs = productConfiguratorPtal.attribute_values\n            .filter(ptav => selectedPtavIds.has(ptav.id))\n            .map(ptav => new ProductTemplateAttributeValue({\n                id: ptav.id,\n                name: ptav.name,\n                price_extra: ptav.price_extra,\n                custom_value: productConfiguratorPtal.customValue,\n            }));\n        return new ProductTemplateAttributeLine({\n            id: productConfiguratorPtal.id,\n            name: productConfiguratorPtal.attribute.name,\n            create_variant: productConfiguratorPtal.create_variant,\n            selected_ptavs: selectedPtavs,\n        });\n    }\n\n    /**\n     * Return the extra price of the selected PTAVs.\n     *\n     * @return {Number} The extra price of the selected PTAVs.\n     */\n    get selectedPtavsPriceExtra() {\n        return this.selected_ptavs.reduce((price, ptav) => price + ptav.price_extra, 0);\n    }\n\n    /**\n     * Check whether this PTAL has selected custom PTAVs.\n     *\n     * @return {Boolean} Whether this PTAL has selected custom PTAVs.\n     */\n    get hasSelectedCustomPtav() {\n        return this.selected_ptavs.some(ptav => ptav.custom_value);\n    }\n\n    /**\n     * Return the display name of this PTAL.\n     *\n     * @return {String} The display name of this PTAL.\n     */\n    get ptalDisplayName() {\n        const selectedPtavNames = this.selected_ptavs.map(ptav => ptav.name).join(', ');\n        let ptalDisplayName = `${this.name}: ${selectedPtavNames}`;\n        if (this.hasSelectedCustomPtav) {\n            ptalDisplayName += ` (${this.selected_ptavs[0].custom_value})`;\n        }\n        return ptalDisplayName;\n    }\n}\n", "export class ProductTemplateAttributeValue {\n    /**\n     * @param {number} id\n     * @param {string} name\n     * @param {number} price_extra\n     * @param {string|undefined} custom_value\n     */\n    constructor({id, name, price_extra, custom_value}) {\n        this.id = id;\n        this.name = name;\n        this.price_extra = price_extra;\n        this.custom_value = custom_value;\n    }\n}\n", "\nimport { Component } from \"@odoo/owl\";\nimport { formatCurrency } from \"@web/core/currency\";\nimport {\n    ProductTemplateAttributeLine as PTAL\n} from \"../product_template_attribute_line/product_template_attribute_line\";\nimport { QuantityButtons } from '../quantity_buttons/quantity_buttons';\nimport { getSelectedCustomPtav } from \"../sale_utils\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport class Product extends Component {\n    static components = { PTAL, QuantityButtons };\n    static template = \"sale.Product\";\n    static props = {\n        id: { type: [Number, {value: false}], optional: true },\n        product_tmpl_id: Number,\n        display_name: String,\n        description_sale: [Boolean, String], // backend sends 'false' when there is no description\n        price: Number,\n        quantity: Number,\n        uom: { type: Object, optional: true },\n        available_uoms: { type: Object, optional: true },\n        attribute_lines: Object,\n        optional: Boolean,\n        imageURL: { type: String, optional: true },\n        archived_combinations: Array,\n        exclusions: Object,\n        parent_exclusions: Object,\n        parent_product_tmpl_id: { type: Number, optional: true },\n        price_info: { type: String, optional: true },\n        selectedComboItems: {\n            type: Array,\n            element: Object,\n            shape: {\n                name: String,\n            },\n            optional: true,\n        },\n    };\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Return the price, in the format of the given currency.\n     *\n     * @return {String} - The price, in the format of the given currency.\n     */\n    getFormattedPrice() {\n        return formatCurrency(this.props.price, this.env.currency.id);\n    }\n\n    /**\n     * Check whether this product is the main product.\n     *\n     * @return {Boolean} - Whether this product is the main product.\n     */\n    get isMainProduct() {\n        return this.env.mainProductTmplId === this.props.product_tmpl_id;\n    }\n\n    /**\n     * Return this product's image URL.\n     *\n     * @return {String} This product's image URL.\n     */\n    get imageUrl() {\n        const modelPath = this.props.id\n            ? `product.product/${ this.props.id }`\n            : `product.template/${ this.props.product_tmpl_id }`;\n        return `/web/image/${ modelPath }/image_256`;\n    }\n\n    /**\n     * Check whether the provided PTAL should be shown.\n     *\n     * @return {Boolean} Whether the PTAL should be shown.\n     */\n    shouldShowPtal(ptal) {\n        return this.env.canChangeVariant\n            || ptal.create_variant === 'no_variant'\n            || !!getSelectedCustomPtav(ptal);\n    }\n\n\n    get UoMTitle() {\n        return _t(\"Packaging\");\n    }\n\n    async selectUoM(event) {\n        this.env.setUoM(this.props.product_tmpl_id, parseInt(event.target.value));\n    }\n\n}\n", "import { Component } from '@odoo/owl';\nimport { BadgeExtraPrice } from '../badge_extra_price/badge_extra_price';\nimport { ProductProduct } from '../models/product_product';\n\nexport class ProductCard extends Component {\n    static template = 'sale.ProductCard';\n    static components = { BadgeExtraPrice };\n    static props = {\n        product: ProductProduct,\n        extraPrice: { type: Number, optional: true },\n        onClick: Function,\n        isSelected: { type: Boolean, optional: true },\n        isConfigurable: { type: Boolean, optional: true }\n    };\n\n    /**\n     * Check whether the provided PTAL should be shown in this card.\n     *\n     * @param {ProductTemplateAttributeLine} ptal The PTAL to check.\n     * @return {Boolean} Whether to show the PTAL.\n     */\n    shouldShowPtal(ptal) {\n        return (\n            ptal.selected_ptavs.length > 0 &&\n            (ptal.hasSelectedCustomPtav || ptal.create_variant === 'no_variant')\n        );\n    }\n}\n", "import { Component, onWillStart, useState, useSubEnv } from \"@odoo/owl\";\nimport { Dialog } from '@web/core/dialog/dialog';\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { ProductList } from \"../product_list/product_list\";\nimport { formatCurrency } from '@web/core/currency';\n\nexport class ProductConfiguratorDialog extends Component {\n    static components = { Dialog, ProductList};\n    static template = 'sale.ProductConfiguratorDialog';\n    static props = {\n        productTemplateId: Number,\n        ptavIds: { type: Array, element: Number },\n        customPtavs: {\n            type: Array,\n            element: Object,\n            shape: {\n                id: Number,\n                value: String,\n            }\n        },\n        quantity: Number,\n        productUOMId: { type: Number, optional: true },\n        companyId: { type: Number, optional: true },\n        pricelistId: { type: Number, optional: true },\n        currencyId: { type: Number, optional: true },\n        selectedComboItems: {\n            type: Array,\n            element: Object,\n            shape: {\n                name: String,\n            },\n            optional: true,\n        },\n        soDate: String,\n        size: {\n            type: String,\n            optional: true,\n            validate: (s) => [\"sm\", \"md\", \"lg\", \"xl\", \"fs\", \"fullscreen\"].includes(s),\n        },\n        edit: { type: Boolean, optional: true },\n        options: {\n            type: Object,\n            optional: true,\n            shape: {\n                canChangeVariant: { type: Boolean, optional: true },\n                showQuantity : { type: Boolean, optional: true },\n                showPrice : { type: Boolean, optional: true },\n                showPackaging: { type: Boolean, optional: true },\n            },\n        },\n        save: Function,\n        discard: Function,\n        close: Function, // This is the close from the env of the Dialog Component\n    };\n    static defaultProps = {\n        edit: false,\n    }\n\n    setup() {\n        this.title = _t(\"Configure your product\");\n        this.env.dialogData.dismiss = !this.props.edit && this.props.discard.bind(this);\n        this.state = useState({\n            products: [],\n            optionalProducts: [],\n        });\n        // Nest the currency id in an object so that it stays up to date in the `env`, even if we\n        // modify it in `onWillStart` afterwards.\n        this.currency = { id: this.props.currencyId };\n        this.getValuesUrl = '/sale/product_configurator/get_values';\n        this.createProductUrl = '/sale/product_configurator/create_product';\n        this.updateCombinationUrl = '/sale/product_configurator/update_combination';\n        this.getOptionalProductsUrl = '/sale/product_configurator/get_optional_products';\n\n        useSubEnv({\n            mainProductTmplId: this.props.productTemplateId,\n            currency: this.currency,\n            canChangeVariant: this.props.options?.canChangeVariant ?? true,\n            showQuantity: this.props.options?.showQuantity ?? true,\n            showPackaging: this.props.options?.showPackaging ?? true,\n            showPrice: this.props.options?.showPrice ?? true,\n            addProduct: this._addProduct.bind(this),\n            removeProduct: this._removeProduct.bind(this),\n            setQuantity: this._setQuantity.bind(this),\n            setUoM: this._setUnitOfMeasure.bind(this),\n            updateProductTemplateSelectedPTAV: this._updateProductTemplateSelectedPTAV.bind(this),\n            updatePTAVCustomValue: this._updatePTAVCustomValue.bind(this),\n            isPossibleCombination: this._isPossibleCombination,\n        });\n\n        onWillStart(async () => {\n            const {\n                products,\n                optional_products,\n                currency_id,\n            } = await this._loadData(this.props.edit);\n\n            // If the product configurator is opened after the combo configurator (which happens if\n            // a combo product has optional products), `_loadData` will return a single product\n            // (i.e. the combo product), which should be linked to the previously selected combo\n            // items.\n            products[0].selectedComboItems = this.props.selectedComboItems || [];\n\n            this.state.products = products;\n            this.state.optionalProducts = optional_products;\n            for (const customPtav of this.props.customPtavs) {\n                this._updatePTAVCustomValue(\n                    this.env.mainProductTmplId,\n                    customPtav.id,\n                    customPtav.value\n                );\n            }\n            this._checkExclusions(this.state.products[0]);\n            // Use the currency id retrieved from the server if none was provided in the props.\n            this.currency.id ??= currency_id;\n        });\n    }\n\n    get totalMessage() {\n        return _t(\"Total: %s\", this.getFormattedTotal());\n    }\n\n    /**\n    * Return the total of the product in the list, in the currency of the `sale.order`.\n    *\n    * @return {String} - The sum of all items in the list, in the currency of the `sale.order`.\n    */\n    getFormattedTotal() {\n        const total = (this.state.products || []).reduce(\n            (sum, product) => sum + product.price * product.quantity,\n            0\n        );\n        return formatCurrency(total, this.currency.id);\n    }\n\n    //--------------------------------------------------------------------------\n    // Data Exchanges\n    //--------------------------------------------------------------------------\n\n    async _loadData(onlyMainProduct) {\n        return rpc(this.getValuesUrl, {\n            product_template_id: this.props.productTemplateId,\n            quantity: this.props.quantity,\n            currency_id: this.currency.id,\n            so_date: this.props.soDate,\n            product_uom_id: this.props.productUOMId,\n            company_id: this.props.companyId,\n            pricelist_id: this.props.pricelistId,\n            ptav_ids: this.props.ptavIds,\n            only_main_product: onlyMainProduct,\n            show_packaging: this.env.showPackaging,\n            ...this._getAdditionalRpcParams(),\n        });\n    }\n\n    async _createProduct(product) {\n        return rpc(this.createProductUrl, {\n            product_template_id: product.product_tmpl_id,\n            ptav_ids: this._getCombination(product),\n        });\n    }\n\n    async _updateCombination(product, quantity, uomId) {\n        return rpc(this.updateCombinationUrl, {\n            product_template_id: product.product_tmpl_id,\n            ptav_ids: this._getCombination(product),\n            currency_id: this.currency.id,\n            so_date: this.props.soDate,\n            quantity: quantity,\n            product_uom_id: uomId,\n            company_id: this.props.companyId,\n            pricelist_id: this.props.pricelistId,\n            ...this._getAdditionalRpcParams(),\n        });\n    }\n\n    async _getOptionalProducts(product) {\n        return rpc(this.getOptionalProductsUrl, {\n            product_template_id: product.product_tmpl_id,\n            ptav_ids: this._getCombination(product),\n            parent_ptav_ids: this._getParentsCombination(product),\n            currency_id: this.currency.id,\n            so_date: this.props.soDate,\n            company_id: this.props.companyId,\n            pricelist_id: this.props.pricelistId,\n            ...this._getAdditionalRpcParams(),\n        });\n    }\n\n    /**\n     * Hook to append additional RPC params in overriding modules.\n     *\n     * @return {Object} - The additional RPC params.\n     */\n    _getAdditionalRpcParams() {\n        return {};\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Add the product to the list of products and fetch his optional products.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     */\n    async _addProduct(productTmplId) {\n        const index = this.state.optionalProducts.findIndex(\n            p => p.product_tmpl_id === productTmplId\n        );\n        if (index >= 0) {\n            this.state.products.push(...this.state.optionalProducts.splice(index, 1));\n            // Fetch optional product from the server with the parent combination.\n            const product = this._findProduct(productTmplId);\n            // Filter out optional products that are already loaded in the configurator.\n            const newOptionalProducts = (await this._getOptionalProducts(product)).filter(\n                p => !this._findProduct(p.product_tmpl_id)\n            );\n            this.state.optionalProducts.push(...newOptionalProducts);\n        }\n    }\n\n    /**\n     * Remove the product and his optional products from the list of products.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     */\n    _removeProduct(productTmplId) {\n        const index = this.state.products.findIndex(p => p.product_tmpl_id === productTmplId);\n        if (index >= 0) {\n            this.state.optionalProducts.push(...this.state.products.splice(index, 1));\n            for (const childProduct of this._getChildProducts(productTmplId)) {\n                this._removeProduct(childProduct.product_tmpl_id);\n                this.state.optionalProducts.splice(\n                    this.state.optionalProducts.findIndex(\n                        p => p.product_tmpl_id === childProduct.product_tmpl_id\n                    ), 1\n                );\n            }\n        }\n    }\n\n    /**\n     * Set the quantity of the product to a given value.\n     *\n     * If the value is less than or equal to zero, the product is removed from the product list\n     * instead, unless it is the main product, in which case the quantity is set to 1.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     * @param {Number} quantity - The new quantity of the product.\n     * @return {Boolean} - Whether the quantity was updated.\n     */\n    async _setQuantity(productTmplId, quantity) {\n        if (quantity <= 0) {\n            if (productTmplId === this.env.mainProductTmplId) {\n                quantity = 1;\n            } else {\n                this._removeProduct(productTmplId);\n                return true;\n            }\n        }\n        const product = this._findProduct(productTmplId);\n        if (product.quantity === quantity) {\n            return false;\n        }\n        const { price } = await this._updateCombination(product, quantity, product.uom.id);\n        product.quantity = quantity;\n        product.price = parseFloat(price);\n\n        return true;\n    }\n\n    /**\n     * Set the uom of the product to a given value.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     * @param {Number} uomId - The new uom of the product, as an `uom.uom` id.\n     *\n     * @return {Boolean} - Whether the uom was updated.\n     */\n    async _setUnitOfMeasure(productTmplId, uomId) {\n        const product = this._findProduct(productTmplId);\n        if (product.uom.id === uomId) {\n            return false;\n        }\n        const { price } = await this._updateCombination(product, product.quantity, uomId);\n        product.price = parseFloat(price);\n        product.uom = product.available_uoms.find((uom) => uom.id === uomId);\n\n        return true;\n    }\n\n    /**\n     * Change the value of `selected_attribute_value_ids` on the given PTAL in the product.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     * @param {Number} ptalId - The PTAL id, as a `product.template.attribute.line` id.\n     * @param {Number} ptavId - The PTAV id, as a `product.template.attribute.value` id.\n     * @param {Boolean} isMulti - Whether multiple `product.template.attribute.value` can be selected.\n     */\n    async _updateProductTemplateSelectedPTAV(productTmplId, ptalId, ptavId, isMulti) {\n        const product = this._findProduct(productTmplId);\n        const ptal = product.attribute_lines.find(line => line.id === ptalId);\n        ptavId = parseInt(ptavId);\n        if (isMulti) {\n            const selectedPtavIds = new Set(ptal.selected_attribute_value_ids);\n            selectedPtavIds.has(ptavId)\n                ? selectedPtavIds.delete(ptavId)\n                : selectedPtavIds.add(ptavId);\n            ptal.selected_attribute_value_ids = Array.from(selectedPtavIds);\n        } else {\n            ptal.selected_attribute_value_ids = [ptavId];\n        }\n        this._checkExclusions(product);\n        if (this._isPossibleCombination(product)) {\n            const updatedValues = await this._updateCombination(product, product.quantity, product.uom.id);\n            Object.assign(product, updatedValues);\n            // When a combination should exist but was deleted from the database, it should not be\n            // selectable and considered as an exclusion.\n            if (!product.id && product.attribute_lines.every(ptal => ptal.create_variant === \"always\")) {\n                const combination = this._getCombination(product);\n                product.archived_combinations = product.archived_combinations.concat([combination]);\n                this._checkExclusions(product);\n            }\n        }\n    }\n\n    /**\n     * Set the custom value for a given custom PTAV.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     * @param {Number} ptavId - The PTAV id, as a `product.template.attribute.value` id.\n     * @param {String} customValue - The custom value.\n     */\n    _updatePTAVCustomValue(productTmplId, ptavId, customValue) {\n        const product = this._findProduct(productTmplId);\n        product.attribute_lines.find(\n            ptal => ptal.selected_attribute_value_ids.includes(ptavId)\n        ).customValue = customValue;\n    }\n\n    /**\n     * Check the exclusions of a given product and his child.\n     *\n     * @param {Object} product - The product for which to check the exclusions.\n     */\n    _checkExclusions(product) {\n        const combination = this._getCombination(product);\n        const exclusions = product.exclusions;\n        const parentExclusions = product.parent_exclusions;\n        const archivedCombinations = product.archived_combinations;\n        const parentCombination = this._getParentsCombination(product);\n        const childProducts = this._getChildProducts(product.product_tmpl_id)\n        const ptavList = product.attribute_lines.flat().flatMap(ptal => ptal.attribute_values)\n        ptavList.map(ptav => ptav.excluded = false); // Reset all the values\n\n        if (exclusions) {\n            for(const ptavId of combination) {\n                for(const excludedPtavId of exclusions[ptavId]) {\n                    ptavList.find(ptav => ptav.id === excludedPtavId).excluded = true;\n                }\n            }\n        }\n        if (parentCombination) {\n            for(const ptavId of parentCombination) {\n                for(const excludedPtavId of (parentExclusions[ptavId]||[])) {\n                    const ptav = ptavList.find(ptav => ptav.id === excludedPtavId);\n                    if (ptav) {\n                        ptav.excluded = true; // Assign only if the element exists\n                    }\n                }\n            }\n        }\n        if (archivedCombinations) {\n            for(const excludedCombination of archivedCombinations) {\n                const ptavCommon = excludedCombination.filter((ptav) => combination.includes(ptav));\n                if (ptavCommon.length === combination.length) {\n                    for(const excludedPtavId of ptavCommon) {\n                        ptavList.find(ptav => ptav.id === excludedPtavId).excluded = true;\n                    }\n                } else if (ptavCommon.length === (combination.length - 1)) {\n                    // In this case we only need to disable the remaining ptav\n                    const disabledPtavId = excludedCombination.find(\n                        (ptav) => !combination.includes(ptav)\n                    );\n                    const excludedPtav = ptavList.find(ptav => ptav.id === disabledPtavId)\n                    if (excludedPtav) {\n                        excludedPtav.excluded = true;\n                    }\n                }\n            }\n        }\n        for(const optionalProductTmpl of childProducts) {\n            this._checkExclusions(optionalProductTmpl);\n        }\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Return the product given his template id.\n     *\n     * @param {Number} productTmplId - The product template id, as a `product.template` id.\n     * @return {Object} - The product.\n     */\n    _findProduct(productTmplId) {\n        // The product might be in either of the two lists `products` or `optional_products`.\n        return  this.state.products.find(p => p.product_tmpl_id === productTmplId) ||\n                this.state.optionalProducts.find(p => p.product_tmpl_id === productTmplId);\n    }\n\n    /**\n     * Return the list of dependents products for a given product.\n     *\n     * @param {Number} productTmplId - The product template id for which to find his children, as a\n     *                                 `product.template` id.\n     * @return {Array} - The list of dependents products.\n     */\n    _getChildProducts(productTmplId) {\n        return [\n            ...this.state.products.filter(p => p.parent_product_tmpl_id === productTmplId),\n            ...this.state.optionalProducts.filter(p => p.parent_product_tmpl_id === productTmplId)\n        ]\n    }\n\n    /**\n     * Return the selected PTAV of the product, as a list of `product.template.attribute.value` id.\n     *\n     * @param {Object} product - The product for which to find the combination.\n     * @return {Array} - The combination of the product.\n     */\n    _getCombination(product) {\n        return product.attribute_lines.flatMap(ptal => ptal.selected_attribute_value_ids);\n    }\n\n    /**\n     * Return the selected PTAVs of the parent product, as a list of\n     * `product.template.attribute.value` ids.\n     *\n     * @param {Object} product - The product for which to find the parent combination.\n     * @return {Array} - The combination of the parent product.\n     */\n    _getParentsCombination(product) {\n        return product.parent_product_tmpl_id\n            ? this._getCombination(this._findProduct(product.parent_product_tmpl_id))\n            : [];\n    }\n\n    /**\n     * Check if a product has a valid combination.\n     *\n     * @param {Object} product - The product for which to check the combination.\n     * @return {Boolean} - Whether the combination is valid or not.\n     */\n    _isPossibleCombination(product) {\n        return product.attribute_lines.every(ptal => {\n            const selectedPtavIds = new Set(ptal.selected_attribute_value_ids);\n            return ptal.attribute_values\n                .filter(ptav => selectedPtavIds.has(ptav.id))\n                .every(ptav => !ptav.excluded);\n        });\n    }\n\n    /**\n     * Check if all the products selected have a valid combination.\n     *\n     * @return {Boolean} - Whether all the products selected have a valid combination or not.\n     */\n    isPossibleConfiguration() {\n        return [...this.state.products].every(\n            p => this._isPossibleCombination(p)\n        );\n    }\n\n    /**\n     * Confirm the current combination(s).\n     *\n     * @return {undefined}\n     */\n    async onConfirm(options) {\n        if (!this.isPossibleConfiguration()) return;\n        // Create the products with dynamic attributes\n        for (const product of this.state.products) {\n            if (\n                !product.id &&\n                product.attribute_lines.some(ptal => ptal.create_variant === \"dynamic\")\n            ) {\n                const productId = await this._createProduct(product);\n                product.id = parseInt(productId);\n            }\n        }\n        await this.props.save(\n            this.state.products.find(\n                p => p.product_tmpl_id === this.env.mainProductTmplId\n            ),\n            this.state.products.filter(\n                p => p.product_tmpl_id !== this.env.mainProductTmplId\n            ),\n            options,\n        );\n        this.props.close();\n    }\n\n    /**\n     * Discard the modal.\n     */\n    onDiscard() {\n        if (!this.props.edit) {\n            this.props.discard(); // clear the line\n        }\n        this.props.close();\n    }\n}\n", "\nimport { Component } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Product } from \"../product/product\";\n\nexport class ProductList extends Component {\n    static components = { Product };\n    static template = \"sale.ProductList\";\n    static props = {\n        products: Array,\n        areProductsOptional: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        areProductsOptional: false,\n    };\n\n    setup() {\n        this.optionalProductsTitle = _t(\"Add optional products\");\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Component } from \"@odoo/owl\";\nimport { formatCurrency } from \"@web/core/currency\";\nimport { BadgeExtraPrice } from \"../badge_extra_price/badge_extra_price\";\nimport { getSelectedCustomPtav } from \"../sale_utils\";\n\nexport class ProductTemplateAttributeLine extends Component {\n    static components = { BadgeExtraPrice };\n    static template = \"sale.ProductTemplateAttributeLine\";\n    static props = {\n        productTmplId: Number,\n        id: Number,\n        attribute: {\n            type: Object,\n            shape: {\n                id: Number,\n                name: String,\n                display_type: {\n                    type: String,\n                    validate: type => [\"color\", \"multi\", \"pills\", \"radio\", \"select\", \"image\"].includes(type),\n                },\n            },\n        },\n        attribute_values: {\n            type: Array,\n            element: {\n                type: Object,\n                shape: {\n                    id: Number,\n                    name: String,\n                    html_color: [Boolean, String], // backend sends 'false' when there is no color\n                    image: [Boolean, String], // backend sends 'false' when there is no image set\n                    is_custom: Boolean,\n                    price_extra: Number,\n                    excluded: { type: Boolean, optional: true },\n                },\n            },\n        },\n        selected_attribute_value_ids: { type: Array, element: Number },\n        create_variant: {\n            type: String,\n            validate: type => [\"always\", \"dynamic\", \"no_variant\"].includes(type),\n        },\n        customValue: {type: [{value: false}, String], optional: true},\n    };\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Update the selected PTAV in the state.\n     *\n     * @param {Event} event\n     */\n    updateSelectedPTAV(event) {\n        this.env.updateProductTemplateSelectedPTAV(\n            this.props.productTmplId, this.props.id, event.target.value, this.props.attribute.display_type == 'multi'\n        );\n    }\n\n    /**\n     * Update in the state the custom value of the selected PTAV.\n     *\n     * @param {Event} event\n     */\n    updateCustomValue(event) {\n        this.env.updatePTAVCustomValue(\n            this.props.productTmplId, this.props.selected_attribute_value_ids[0], event.target.value\n        );\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Return template name to use by checking the display type in the props.\n     *\n     * Each attribute line can have one of this five display types:\n     *      - 'Color'  : Display each attribute as a circle filled with said color.\n     *      - 'Pills'  : Display each attribute as a rectangle-shaped element.\n     *      - 'Radio'  : Display each attribute as a radio element.\n     *      - 'Select' : Display each attribute in a selection tag.\n     *      - 'Multi'  : Display each attribute in a multi-checkbox tag.\n     *\n     * @return {String} - The template name to use.\n     */\n    getPTAVTemplate() {\n        switch(this.props.attribute.display_type) {\n            case 'select':\n                return 'sale.ptav_select';\n            case 'radio':\n                return 'sale.ptav_radio';\n            case 'pills':\n                return 'sale.ptav_pills';\n            case 'color':\n                return 'sale.ptav_color';\n            case 'multi':\n                return 'sale.ptav_multi';\n            case 'image':\n                return 'sale.ptav_image';\n        }\n    }\n\n    /**\n     * Return the name of the PTAV\n     *\n     * In the selection HTML tag, it is impossible to show the component `BadgeExtraPrice`. Append\n     * the extra price to the name to ensure that the extra price will be shown.\n     * Note: used in `sale.ptav_select`.\n     *\n     * @param {Object} ptav - The attribute, as a `product.template.attribute.value` summary dict.\n     * @return {String} - The name of the PTAV.\n     */\n    getPTAVSelectName(ptav) {\n        if (ptav.price_extra) {\n            const sign = ptav.price_extra > 0 ? '+' : '-';\n            const price = formatCurrency(Math.abs(ptav.price_extra), this.env.currency.id);\n            return ptav.name +\" (\"+ sign + \" \" + price + \")\";\n        } else {\n            return ptav.name;\n        }\n    }\n\n    /**\n     * Check if the selected ptav is custom or not.\n     *\n     * @return {Boolean} - Whether the selected ptav is custom or not.\n     */\n    isSelectedPTAVCustom() {\n        return !!getSelectedCustomPtav(this.props);\n    }\n\n    get showValuesChoice() {\n        return (this.env.canChangeVariant || this.props.create_variant === 'no_variant') && (\n            this.props.attribute_values.length > 1 || this.props.attribute.display_type === 'multi'\n        )\n    }\n\n    get customValuePlaceholder() {\n        return _t(\"Enter a customized value\");\n    }\n\n    /**\n     * Check if the line has a custom ptav or not.\n     *\n     * @return {Boolean} - Whether the line has a custom ptav or not.\n     */\n    hasPTAVCustom() {\n        return this.props.attribute_values.some(\n            ptav => ptav.is_custom\n        );\n    }\n }\n", "\nimport { Component } from '@odoo/owl';\n\nexport class QuantityButtons extends Component {\n    static template = 'sale.QuantityButtons';\n    static props = {\n        quantity: Number,\n        setQuantity: Function,\n        isMinusButtonDisabled: { type: Boolean, optional: true },\n        isPlusButtonDisabled: { type: Boolean, optional: true },\n        btnClasses: { type: String, optional: true },\n    };\n\n    /**\n     * Increase the quantity.\n     */\n    increaseQuantity() {\n        this.props.setQuantity(this.props.quantity + 1);\n    }\n\n    /**\n     * Decrease the quantity.\n     */\n    decreaseQuantity() {\n        this.props.setQuantity(this.props.quantity - 1);\n    }\n\n    /**\n     * Set the quantity to a specified value.\n     *\n     * @param {Event} event The quantity input's `on change` event, containing the new quantity.\n     */\n    async setQuantity(event) {\n        const quantity = parseFloat(event.target.value);\n        const didUpdateQuantity = await this.props.setQuantity(isNaN(quantity) ? 0 : quantity);\n        // If the quantity wasn't updated, the component won't rerender, and the input will display\n        // a stale value. As a result, we need to manually rerender the input.\n        if (!didUpdateQuantity) {\n            this.render();\n        }\n    }\n}\n", "/**\n * Checks whether the 2 provided sale order lines are linked.\n *\n * @param linkingSaleOrderLine The line that is linking to the other line.\n * @param linkedSaleOrderLine The line that is linked by the other line.\n * @return {Boolean} Whether the 2 lines are linked.\n */\nexport function areSaleOrderLinesLinked(linkingSaleOrderLine, linkedSaleOrderLine) {\n    const linkingId = linkedSaleOrderLine.isNew\n        ? linkingSaleOrderLine.data.linked_virtual_id\n        : linkingSaleOrderLine.data.linked_line_id.id;\n    const linkedId = linkedSaleOrderLine.isNew\n        ? linkedSaleOrderLine.data.virtual_id\n        : linkedSaleOrderLine.resId;\n    return linkingId && linkingId === linkedId;\n}\n\n/**\n * Gets the linked lines of the provided sale order line.\n *\n * @param saleOrderLine The line whose linked lines to get.\n * @return {Object[]} The list of linked lines.\n */\nexport function getLinkedSaleOrderLines(saleOrderLine) {\n    const saleOrder = saleOrderLine.model.root;\n    // TODO(loti): this leaves out any combo items that are on another page.\n    return saleOrder.data.order_line.records.filter(\n        record => areSaleOrderLinesLinked(record, saleOrderLine)\n    );\n}\n\n/**\n * Serialize a combo item into a format understandable by the server.\n *\n * @param {ProductComboItem} comboItem The combo item to serialize.\n * @return {Object} The serialized combo item.\n */\nexport function serializeComboItem(comboItem) {\n    return {\n        combo_item_id: comboItem.id,\n        product_id: comboItem.product.id,\n        no_variant_attribute_value_ids: comboItem.product.selectedNoVariantPtavIds,\n        product_custom_attribute_values: comboItem.product.selectedCustomPtavs.map(\n            customPtav => ({\n                custom_product_template_attribute_value_id: customPtav.id,\n                custom_value: customPtav.value,\n            })\n        ),\n    }\n}\n\n/**\n * Get the selected custom PTAV in the provided PTAL, if any.\n *\n * Note: a PTAL can have at most one selected custom PTAV, by design.\n *\n * @param {ProductTemplateAttributeLine.props} ptal The PTAL in which to look for the selected\n *     custom PTAV.\n * @return {Object|undefined} The selected custom PTAV, if any.\n *\n */\nexport function getSelectedCustomPtav(ptal) {\n    const selectedPtavIds = new Set(ptal.selected_attribute_value_ids);\n    return ptal.attribute_values.find(ptav => ptav.is_custom && selectedPtavIds.has(ptav.id));\n}\n", "import {\n    ComboConfiguratorDialog\n} from '@sale/js/combo_configurator_dialog/combo_configurator_dialog';\nimport { _t } from '@web/core/l10n/translation';\nimport { patch } from '@web/core/utils/patch';\n\npatch(ComboConfiguratorDialog, {\n    props: {\n        ...ComboConfiguratorDialog.props,\n        isFrontend: { type: Boolean, optional: true },\n        options: {\n            ...ComboConfiguratorDialog.props.options,\n            shape: {\n                ...ComboConfiguratorDialog.props.options.shape,\n                isBuyNow: { type: Boolean, optional: true },\n            },\n        },\n    },\n});\n\npatch(ComboConfiguratorDialog.prototype, {\n    setup() {\n        super.setup(...arguments);\n\n        if (this.props.isFrontend) {\n            this.getPriceUrl = '/website_sale/combo_configurator/get_price';\n        }\n    },\n\n    get totalMessage() {\n        if (this.props.isFrontend) {\n            return _t(\"Total: %s\", this.formattedTotalPrice);\n        }\n        return super.totalMessage(...arguments);\n    },\n\n    get _comboProductData() {\n        const comboProductData = super._comboProductData;\n        if (this.props.isFrontend) {\n            Object.assign(comboProductData, { 'price': this._comboPrice });\n        }\n        return comboProductData;\n    },\n\n    _getAdditionalDialogProps() {\n        const props = super._getAdditionalDialogProps();\n        if (this.props.isFrontend) {\n            props.isFrontend = this.props.isFrontend;\n        }\n        return props;\n    },\n});\n", "import { Product } from '@sale/js/product/product';\nimport { formatCurrency } from '@web/core/currency';\nimport { patch } from '@web/core/utils/patch';\n\npatch(Product, {\n    props: {\n        ...Product.props,\n        strikethrough_price: { type: Number, optional: true },\n        can_be_sold: { type: Boolean, optional: true },\n        // The following fields are needed for tracking.\n        category_name: { type: String, optional: true },\n        currency_name: { type: String, optional: true },\n    },\n});\n\npatch(Product.prototype, {\n    /**\n     * Return the strikethrough price, formatted using the environment's currency.\n     *\n     * @return {String} - The formatted strikethrough price.\n     */\n    get formattedStrikethroughPrice() {\n        return formatCurrency(this.props.strikethrough_price, this.env.currency.id);\n    },\n});\n", "import { useSubEnv } from '@odoo/owl';\nimport {\n    ProductConfiguratorDialog\n} from '@sale/js/product_configurator_dialog/product_configurator_dialog';\nimport { _t } from '@web/core/l10n/translation';\nimport { patch } from '@web/core/utils/patch';\n\npatch(ProductConfiguratorDialog, {\n    props: {\n        ...ProductConfiguratorDialog.props,\n        isFrontend: { type: Boolean, optional: true },\n        options: {\n            ...ProductConfiguratorDialog.props.options,\n            shape: {\n                ...ProductConfiguratorDialog.props.options.shape,\n                isMainProductConfigurable: { type: Boolean, optional: true },\n                isBuyNow: { type: Boolean, optional: true },\n            },\n        },\n    },\n});\n\npatch(ProductConfiguratorDialog.prototype, {\n    setup() {\n        super.setup(...arguments);\n\n        if (this.props.isFrontend) {\n            this.getValuesUrl = '/website_sale/product_configurator/get_values';\n            this.createProductUrl = '/website_sale/product_configurator/create_product';\n            this.updateCombinationUrl = '/website_sale/product_configurator/update_combination';\n            this.getOptionalProductsUrl = '/website_sale/product_configurator/get_optional_products';\n            this.title = _t(\"Configure\");\n        }\n\n        useSubEnv({\n            isFrontend: this.props.isFrontend,\n            isMainProductConfigurable: this.props.options?.isMainProductConfigurable ?? true,\n        });\n    },\n\n    /**\n     * Check whether all selected products can be sold.\n     *\n     * @return {Boolean} - Whether all selected products can be sold.\n     */\n    canBeSold() {\n        return this.state.products.every(p => p.can_be_sold);\n    },\n\n    /**\n     * Check whether to show the \"shop\" buttons in the dialog footer.\n     *\n     * @return {Boolean} - Whether to show the \"shop\" buttons in the dialog footer.\n     */\n    showShopButtons() {\n        return this.props.isFrontend && !this.props.edit;\n    },\n\n    get totalMessage() {\n        if (this.env.isFrontend) {\n            // To be translated, the title must be repeated here. Indeed, only\n            // translations of \"frontend modules\" are fetched in the context of\n            // website. The original definition of the title is in \"sale\", which\n            // is not a frontend module.\n            return _t(\"Total: %s\", this.getFormattedTotal());\n        }\n        return super.totalMessage(...arguments);\n    },\n\n});\n", "import { ProductList } from '@sale/js/product_list/product_list';\nimport { _t } from \"@web/core/l10n/translation\";\nimport { patch } from '@web/core/utils/patch';\n\npatch(ProductList.prototype, {\n    setup() {\n        super.setup(...arguments);\n\n        if (this.env.isFrontend) {\n            this.optionalProductsTitle = _t(\"Options\");\n        }\n    },\n});\n", "import { _t } from '@web/core/l10n/translation';\nimport {\n    ProductTemplateAttributeLine\n} from '@sale/js/product_template_attribute_line/product_template_attribute_line';\nimport { patch } from '@web/core/utils/patch';\n\npatch(ProductTemplateAttributeLine.prototype, {\n    /**\n     * Return the display name of this PTAL.\n     *\n     * @return {String} - The display name of this PTAL.\n     */\n    getPtalDisplayName() {\n        const selectedPtavIds = new Set(this.props.selected_attribute_value_ids);\n        const selectedPtavNames = this.props.attribute_values\n            .filter(ptav => selectedPtavIds.has(ptav.id))\n            .map(ptav => ptav.name)\n            .join(', ');\n        let ptalDisplayName = `${this.props.attribute.name}: ${selectedPtavNames}`;\n        if (this.isSelectedPTAVCustom()) {\n            ptalDisplayName += `: ${this.props.customValue}`;\n        }\n        return ptalDisplayName;\n    },\n\n    get customValuePlaceholder() {\n        // The original definition of this placeholder is in `sale` module which is not a frontend module. However, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Enter a customized value\");\n    },\n});\n", "import {\n    Location\n} from '@delivery/js/location_selector/location/location';\nimport { patch } from '@web/core/utils/patch';\nimport { _t } from '@web/core/l10n/translation';\n\npatch(Location.prototype, {\n    get openingHoursLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Opening hours\");\n    },\n});\n", "import {\n    LocationSchedule\n} from '@delivery/js/location_selector/location_schedule/location_schedule';\nimport { patch } from '@web/core/utils/patch';\nimport { _t } from '@web/core/l10n/translation';\n\npatch(LocationSchedule.prototype, {\n    get closedLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Closed\");\n    },\n});\n", "import {\n    LocationSelectorDialog\n} from '@delivery/js/location_selector/location_selector_dialog/location_selector_dialog';\nimport { patch } from '@web/core/utils/patch';\nimport { _t } from '@web/core/l10n/translation';\n\npatch(LocationSelectorDialog, {\n    props: {\n        ...LocationSelectorDialog.props,\n        orderId: { type: Number, optional: true },\n        isFrontend: { type: Boolean, optional: true },\n    },\n});\n\npatch(LocationSelectorDialog.prototype, {\n    setup() {\n        super.setup(...arguments);\n\n        if (this.props.isFrontend) {\n            this.getLocationUrl = '/website_sale/get_pickup_locations';\n        }\n    },\n\n    get title() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        if (this.state.locations.length === 1) {\n            return _t(\"Pickup Location\")\n        }\n        return _t(\"Choose a pick-up point\");\n    },\n\n    get validationButtonLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Choose this location\");\n    },\n\n    get postalCodePlaceholder() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Your postal code\");\n    },\n\n    get listViewButtonLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"List view\");\n    },\n\n    get mapViewButtonLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Map view\");\n    },\n\n    get errorMessage() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"No result\");\n    },\n\n    get loadingMessage() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Loading...\");\n    },\n});\n", "import {\n    MapContainer\n} from '@delivery/js/location_selector/map_container/map_container';\nimport { patch } from '@web/core/utils/patch';\nimport { _t } from '@web/core/l10n/translation';\n\npatch(MapContainer.prototype, {\n    get errorMessage() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"There was an error loading the map\");\n    },\n\n    get chooseLocationButtonLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Choose this location\");\n    },\n\n    get openingHoursLabel() {\n        // The original definition of this getter is in `delivery` module which is not a frontend module. This problem happens in the context of the website. So, it should be repeated here as translations are only fetched in the context of a frontend module, which is `website_sale` in this case.\n        return _t(\"Opening hours\");\n    },\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { rpc } from \"@web/core/network/rpc\";\n\nexport class UnsplashBeacon extends Interaction {\n    static selector = \"#wrapwrap\";\n\n    async willStart() {\n        const unsplashImageEls = this.el.querySelectorAll(\"img[src*='/unsplash/']\");\n        const unsplashImageIds = [];\n        for (const unsplashImageEl of unsplashImageEls) {\n            // extract the image id from URL \n            // (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`)\n            unsplashImageIds.push(unsplashImageEl.src.split(\"/unsplash/\")[1].split(\"/\")[0]);\n        }\n\n        if (unsplashImageIds.length) {\n            const appID = await this.waitFor(rpc(\"/web_unsplash/get_app_id\"));\n\n            if (appID) {\n                const fetchURL = new URL(\"https://views.unsplash.com/v\");\n                fetchURL.search = new URLSearchParams({\n                    \"photo_id\": unsplashImageIds.join(\",\"),\n                    \"app_id\": appID,\n                });\n                fetch(fetchURL);\n            }\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"web_unsplash.unsplash_beacon\", UnsplashBeacon);\n", "/* global OdooFin */\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { loadJS } from \"@web/core/assets\";\nimport { post } from \"@web/core/network/http_service\";\n\nexport class OnlineSyncPortal extends Interaction {\n    static selector = \".oe_online_sync #renew_consent_button\";\n    dynamicContent = {\n        _root: {\n            \"t-on-click.prevent.withTarget\": this.onRenewConsentClick,\n        },\n    };\n\n    async OdooFinConnector(action) {\n        // Ensure that the proxyMode is valid\n        const modeRegexp = /^[a-z0-9-_]+$/i;\n        if (!modeRegexp.test(action.params.proxyMode)) {\n            return;\n        }\n\n        await loadJS(`https://${action.params.proxyMode}.odoofin.com/proxy/v1/odoofin_link`);\n\n        // Create and open the iframe\n        const params = {\n            data: action.params,\n            proxyMode: action.params.proxyMode,\n            onEvent: function (event, data) {\n                if (event === \"success\") {\n                    const processUrl = `${window.location.pathname}/complete${window.location.search}`;\n                    const reconnectEls = document.querySelectorAll(\".js_reconnect\");\n                    for (const reconnectEl of reconnectEls) {\n                        reconnectEl.classList.toggle(\"d-none\");\n                    }\n                    post(processUrl, { csrf_token: odoo.csrf_token });\n                }\n            },\n        };\n\n        OdooFin.create(params);\n        OdooFin.open();\n    }\n\n    /**\n     * @param {PointerEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onRenewConsentClick(ev, currentTargetEl) {\n        const action = JSON.parse(currentTargetEl.getAttribute(\"iframe-params\"));\n        this.OdooFinConnector(action);\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"account_online_synchronization.online_sync_portal\", OnlineSyncPortal);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class InvoiceSendingMethod extends Interaction {\n    static selector = \".o_portal_details select[name='invoice_sending_method']\";\n    dynamicContent = {\n        _root: { \"t-on-change\": this.showPeppolConfig },\n    };\n\n    start() {\n        this.showPeppolConfig();\n    }\n\n    showPeppolConfig() {\n        const method = this.el.value;\n        const peppolToggleEls = document.querySelectorAll(\".portal_peppol_toggle\");\n        for (const peppolToggleEl of peppolToggleEls) {\n            peppolToggleEl.classList.toggle(\"d-none\", method !== \"peppol\");\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"account_peppol.invoice_sending_method\", InvoiceSendingMethod);\n", "/** @odoo-module **/\n\n/*!\n  * simplewebauthn/browser@9.0.1 (https://github.com/MasterKale/SimpleWebAuthn)\n  * Copyright 2020 Matthew Miller\n  * Licensed under MIT (https://github.com/MasterKale/SimpleWebAuthn/blob/master/LICENSE.md)\n  */\n\nfunction utf8StringToBuffer(value) {\n    return new TextEncoder().encode(value);\n}\n\nfunction bufferToBase64URLString(buffer) {\n    const bytes = new Uint8Array(buffer);\n    let str = '';\n    for (const charCode of bytes) {\n        str += String.fromCharCode(charCode);\n    }\n    const base64String = btoa(str);\n    return base64String.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nfunction base64URLStringToBuffer(base64URLString) {\n    const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');\n    const padLength = (4 - (base64.length % 4)) % 4;\n    const padded = base64.padEnd(base64.length + padLength, '=');\n    const binary = atob(padded);\n    const buffer = new ArrayBuffer(binary.length);\n    const bytes = new Uint8Array(buffer);\n    for (let i = 0; i < binary.length; i++) {\n        bytes[i] = binary.charCodeAt(i);\n    }\n    return buffer;\n}\n\nfunction browserSupportsWebAuthn() {\n    return (window?.PublicKeyCredential !== undefined &&\n        typeof window.PublicKeyCredential === 'function');\n}\n\nfunction toPublicKeyCredentialDescriptor(descriptor) {\n    const { id } = descriptor;\n    return {\n        ...descriptor,\n        id: base64URLStringToBuffer(id),\n        transports: descriptor.transports,\n    };\n}\n\nfunction isValidDomain(hostname) {\n    return (hostname === 'localhost' ||\n        /^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$/i.test(hostname));\n}\n\nclass WebAuthnError extends Error {\n    constructor({ message, code, cause, name, }) {\n        super(message, { cause });\n        this.name = name ?? cause.name;\n        this.code = code;\n    }\n}\n\nfunction identifyRegistrationError({ error, options, }) {\n    const { publicKey } = options;\n    if (!publicKey) {\n        throw Error('options was missing required publicKey property');\n    }\n    if (error.name === 'AbortError') {\n        if (options.signal instanceof AbortSignal) {\n            return new WebAuthnError({\n                message: 'Registration ceremony was sent an abort signal',\n                code: 'ERROR_CEREMONY_ABORTED',\n                cause: error,\n            });\n        }\n    }\n    else if (error.name === 'ConstraintError') {\n        if (publicKey.authenticatorSelection?.requireResidentKey === true) {\n            return new WebAuthnError({\n                message: 'Discoverable credentials were required but no available authenticator supported it',\n                code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT',\n                cause: error,\n            });\n        }\n        else if (publicKey.authenticatorSelection?.userVerification === 'required') {\n            return new WebAuthnError({\n                message: 'User verification was required but no available authenticator supported it',\n                code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT',\n                cause: error,\n            });\n        }\n    }\n    else if (error.name === 'InvalidStateError') {\n        return new WebAuthnError({\n            message: 'The authenticator was previously registered',\n            code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',\n            cause: error,\n        });\n    }\n    else if (error.name === 'NotAllowedError') {\n        return new WebAuthnError({\n            message: error.message,\n            code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',\n            cause: error,\n        });\n    }\n    else if (error.name === 'NotSupportedError') {\n        const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === 'public-key');\n        if (validPubKeyCredParams.length === 0) {\n            return new WebAuthnError({\n                message: 'No entry in pubKeyCredParams was of type \"public-key\"',\n                code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS',\n                cause: error,\n            });\n        }\n        return new WebAuthnError({\n            message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms',\n            code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG',\n            cause: error,\n        });\n    }\n    else if (error.name === 'SecurityError') {\n        const effectiveDomain = window.location.hostname;\n        if (!isValidDomain(effectiveDomain)) {\n            return new WebAuthnError({\n                message: `${window.location.hostname} is an invalid domain`,\n                code: 'ERROR_INVALID_DOMAIN',\n                cause: error,\n            });\n        }\n        else if (publicKey.rp.id !== effectiveDomain) {\n            return new WebAuthnError({\n                message: `The RP ID \"${publicKey.rp.id}\" is invalid for this domain`,\n                code: 'ERROR_INVALID_RP_ID',\n                cause: error,\n            });\n        }\n    }\n    else if (error.name === 'TypeError') {\n        if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {\n            return new WebAuthnError({\n                message: 'User ID was not between 1 and 64 characters',\n                code: 'ERROR_INVALID_USER_ID_LENGTH',\n                cause: error,\n            });\n        }\n    }\n    else if (error.name === 'UnknownError') {\n        return new WebAuthnError({\n            message: 'The authenticator was unable to process the specified options, or could not create a new credential',\n            code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',\n            cause: error,\n        });\n    }\n    return error;\n}\n\nclass BaseWebAuthnAbortService {\n    createNewAbortSignal() {\n        if (this.controller) {\n            const abortError = new Error('Cancelling existing WebAuthn API call for new one');\n            abortError.name = 'AbortError';\n            this.controller.abort(abortError);\n        }\n        const newController = new AbortController();\n        this.controller = newController;\n        return newController.signal;\n    }\n    cancelCeremony() {\n        if (this.controller) {\n            const abortError = new Error('Manually cancelling existing WebAuthn API call');\n            abortError.name = 'AbortError';\n            this.controller.abort(abortError);\n            this.controller = undefined;\n        }\n    }\n}\nconst WebAuthnAbortService = new BaseWebAuthnAbortService();\n\nconst attachments = ['cross-platform', 'platform'];\nfunction toAuthenticatorAttachment(attachment) {\n    if (!attachment) {\n        return;\n    }\n    if (attachments.indexOf(attachment) < 0) {\n        return;\n    }\n    return attachment;\n}\n\nasync function startRegistration(creationOptionsJSON) {\n    if (!browserSupportsWebAuthn()) {\n        throw new Error('WebAuthn is not supported in this browser');\n    }\n    const publicKey = {\n        ...creationOptionsJSON,\n        challenge: base64URLStringToBuffer(creationOptionsJSON.challenge),\n        user: {\n            ...creationOptionsJSON.user,\n            id: utf8StringToBuffer(creationOptionsJSON.user.id),\n        },\n        excludeCredentials: creationOptionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor),\n    };\n    const options = { publicKey };\n    options.signal = WebAuthnAbortService.createNewAbortSignal();\n    let credential;\n    try {\n        credential = (await navigator.credentials.create(options));\n    }\n    catch (err) {\n        throw identifyRegistrationError({ error: err, options });\n    }\n    if (!credential) {\n        throw new Error('Registration was not completed');\n    }\n    const { id, rawId, response, type } = credential;\n    let transports = undefined;\n    if (typeof response.getTransports === 'function') {\n        transports = response.getTransports();\n    }\n    let responsePublicKeyAlgorithm = undefined;\n    if (typeof response.getPublicKeyAlgorithm === 'function') {\n        try {\n            responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();\n        }\n        catch (error) {\n            warnOnBrokenImplementation('getPublicKeyAlgorithm()', error);\n        }\n    }\n    let responsePublicKey = undefined;\n    if (typeof response.getPublicKey === 'function') {\n        try {\n            const _publicKey = response.getPublicKey();\n            if (_publicKey !== null) {\n                responsePublicKey = bufferToBase64URLString(_publicKey);\n            }\n        }\n        catch (error) {\n            warnOnBrokenImplementation('getPublicKey()', error);\n        }\n    }\n    let responseAuthenticatorData;\n    if (typeof response.getAuthenticatorData === 'function') {\n        try {\n            responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());\n        }\n        catch (error) {\n            warnOnBrokenImplementation('getAuthenticatorData()', error);\n        }\n    }\n    return {\n        id,\n        rawId: bufferToBase64URLString(rawId),\n        response: {\n            attestationObject: bufferToBase64URLString(response.attestationObject),\n            clientDataJSON: bufferToBase64URLString(response.clientDataJSON),\n            transports,\n            publicKeyAlgorithm: responsePublicKeyAlgorithm,\n            publicKey: responsePublicKey,\n            authenticatorData: responseAuthenticatorData,\n        },\n        type,\n        clientExtensionResults: credential.getClientExtensionResults(),\n        authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),\n    };\n}\nfunction warnOnBrokenImplementation(methodName, cause) {\n    console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.\\n`, cause);\n}\n\nfunction bufferToUTF8String(value) {\n    return new TextDecoder('utf-8').decode(value);\n}\n\nfunction browserSupportsWebAuthnAutofill() {\n    const globalPublicKeyCredential = window\n        .PublicKeyCredential;\n    if (globalPublicKeyCredential.isConditionalMediationAvailable === undefined) {\n        return new Promise((resolve) => resolve(false));\n    }\n    return globalPublicKeyCredential.isConditionalMediationAvailable();\n}\n\nfunction identifyAuthenticationError({ error, options, }) {\n    const { publicKey } = options;\n    if (!publicKey) {\n        throw Error('options was missing required publicKey property');\n    }\n    if (error.name === 'AbortError') {\n        if (options.signal instanceof AbortSignal) {\n            return new WebAuthnError({\n                message: 'Authentication ceremony was sent an abort signal',\n                code: 'ERROR_CEREMONY_ABORTED',\n                cause: error,\n            });\n        }\n    }\n    else if (error.name === 'NotAllowedError') {\n        return new WebAuthnError({\n            message: error.message,\n            code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',\n            cause: error,\n        });\n    }\n    else if (error.name === 'SecurityError') {\n        const effectiveDomain = window.location.hostname;\n        if (!isValidDomain(effectiveDomain)) {\n            return new WebAuthnError({\n                message: `${window.location.hostname} is an invalid domain`,\n                code: 'ERROR_INVALID_DOMAIN',\n                cause: error,\n            });\n        }\n        else if (publicKey.rpId !== effectiveDomain) {\n            return new WebAuthnError({\n                message: `The RP ID \"${publicKey.rpId}\" is invalid for this domain`,\n                code: 'ERROR_INVALID_RP_ID',\n                cause: error,\n            });\n        }\n    }\n    else if (error.name === 'UnknownError') {\n        return new WebAuthnError({\n            message: 'The authenticator was unable to process the specified options, or could not create a new assertion signature',\n            code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',\n            cause: error,\n        });\n    }\n    return error;\n}\n\nasync function startAuthentication(requestOptionsJSON, useBrowserAutofill = false) {\n    if (!browserSupportsWebAuthn()) {\n        throw new Error('WebAuthn is not supported in this browser');\n    }\n    let allowCredentials;\n    if (requestOptionsJSON.allowCredentials?.length !== 0) {\n        allowCredentials = requestOptionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);\n    }\n    const publicKey = {\n        ...requestOptionsJSON,\n        challenge: base64URLStringToBuffer(requestOptionsJSON.challenge),\n        allowCredentials,\n    };\n    const options = {};\n    if (useBrowserAutofill) {\n        if (!(await browserSupportsWebAuthnAutofill())) {\n            throw Error('Browser does not support WebAuthn autofill');\n        }\n        const eligibleInputs = document.querySelectorAll('input[autocomplete$=\\'webauthn\\']');\n        if (eligibleInputs.length < 1) {\n            throw Error('No <input> with \"webauthn\" as the only or last value in its `autocomplete` attribute was detected');\n        }\n        options.mediation = 'conditional';\n        publicKey.allowCredentials = [];\n    }\n    options.publicKey = publicKey;\n    options.signal = WebAuthnAbortService.createNewAbortSignal();\n    let credential;\n    try {\n        credential = (await navigator.credentials.get(options));\n    }\n    catch (err) {\n        throw identifyAuthenticationError({ error: err, options });\n    }\n    if (!credential) {\n        throw new Error('Authentication was not completed');\n    }\n    const { id, rawId, response, type } = credential;\n    let userHandle = undefined;\n    if (response.userHandle) {\n        userHandle = bufferToUTF8String(response.userHandle);\n    }\n    return {\n        id,\n        rawId: bufferToBase64URLString(rawId),\n        response: {\n            authenticatorData: bufferToBase64URLString(response.authenticatorData),\n            clientDataJSON: bufferToBase64URLString(response.clientDataJSON),\n            signature: bufferToBase64URLString(response.signature),\n            userHandle,\n        },\n        type,\n        clientExtensionResults: credential.getClientExtensionResults(),\n        authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),\n    };\n}\n\nfunction platformAuthenticatorIsAvailable() {\n    if (!browserSupportsWebAuthn()) {\n        return new Promise((resolve) => resolve(false));\n    }\n    return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n}\n\nexport { WebAuthnAbortService, WebAuthnError, base64URLStringToBuffer, browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, bufferToBase64URLString, platformAuthenticatorIsAvailable, startAuthentication, startRegistration };\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { rpc } from \"@web/core/network/rpc\";\nimport * as passkeyLib from \"../../lib/simplewebauthn.js\";\n\nexport class PasskeyLogin extends Interaction {\n    static selector = \".passkey_login_link\";\n    dynamicContent = {\n        _root: { \"t-on-click\": this.onClick },\n    };\n\n    async onClick() {\n        const serverOptions = await this.waitFor(rpc(\"/auth/passkey/start-auth\"));\n        const auth = await this.waitFor(passkeyLib.startAuthentication(serverOptions).catch(e => console.error(e)));\n        if (!auth) {\n            return false;\n        }\n        const form = document.querySelector(\"form.oe_login_form\");\n        form.querySelector(\"input[name='webauthn_response']\").value = JSON.stringify(auth);\n        form.querySelector(\"input[name='type']\").value = \"webauthn\";\n        form.submit();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"auth_passkey.passkey_login\", PasskeyLogin);\n", "import { handleCheckIdentity } from \"@portal/interactions/portal_security\";\r\nimport { Interaction } from \"@web/public/interaction\";\r\nimport { InputConfirmationDialog } from \"@portal/js/components/input_confirmation_dialog/input_confirmation_dialog\";\r\nimport { registry } from \"@web/core/registry\";\r\nimport { renderToMarkup } from \"@web/core/utils/render\";\r\nimport { _t } from \"@web/core/l10n/translation\";\r\n\r\nexport class PortalPasskey extends Interaction {\r\n    static selector = \".o_passkey_portal_entry\";\r\n    dynamicContent = {\r\n        \".o_passkey_portal_rename\": {\r\n            \"t-on-click\": this.onRename,\r\n        },\r\n        \".o_passkey_portal_delete\": {\r\n            \"t-on-click\": this.onDelete,\r\n        },\r\n    };\r\n\r\n    setup() {\r\n        this.id = parseInt(this.el.attributes.id.value);\r\n        this.name = this.el.querySelector(\".o_passkey_name\").innerText;\r\n        this.dropDown = this.el.querySelector(\".o_passkey_dropdown\");\r\n    }\r\n\r\n    async onRename() {\r\n        this.services.dialog.add(InputConfirmationDialog, {\r\n            title: _t(\"Passkeys\"),\r\n            body: renderToMarkup(\"auth_passkey_portal.rename\", { oldname: this.name }),\r\n            confirmLabel: _t(\"Rename\"),\r\n            confirm: async ({ inputEl }) => {\r\n                const name = inputEl.value;\r\n                if (name.length > 0) {\r\n                    await this.services.orm.write(\"auth.passkey.key\", [this.id], { name })\r\n                    location.reload();\r\n                }\r\n            },\r\n            cancelLabel: _t(\"Discard\"),\r\n            cancel: () => {},\r\n        });\r\n    }\r\n\r\n    async onDelete() {\r\n        await handleCheckIdentity(\r\n            this.services.orm.call(\"auth.passkey.key\", \"action_delete_passkey\", [this.id]),\r\n            this.services.orm,\r\n            this.services.dialog\r\n        );\r\n        location.reload();\r\n    }\r\n}\r\n\r\nregistry.category(\"public.interactions\").add(\"auth_passkey_portal.passkey\", PortalPasskey);\r\n", "import { Interaction } from \"@web/public/interaction\";\nimport { InputConfirmationDialog } from \"@portal/js/components/input_confirmation_dialog/input_confirmation_dialog\";\nimport { registry } from \"@web/core/registry\";\nimport { renderToMarkup } from \"@web/core/utils/render\";\nimport { handleCheckIdentity } from \"@portal/interactions/portal_security\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport * as passkeyLib from \"@auth_passkey/../lib/simplewebauthn\";\nimport { user } from \"@web/core/user\";\n\nexport class PortalPasskeyCreate extends Interaction {\n    static selector = \"#portal_passkey_add\";\n    dynamicContent = {\n        _root: { \"t-on-click\": this.startRegistrationFlow },\n    };\n\n    async startRegistrationFlow() {\n        const create_action = await this.waitFor(\n            handleCheckIdentity(\n                this.waitFor(\n                    this.services.orm.call(\"res.users\", \"action_create_passkey\", [user.userId])\n                ),\n                this.services.orm,\n                this.services.dialog\n            )\n        );\n        const serverOptions = create_action.context.registration;\n        this.services.dialog.add(InputConfirmationDialog, {\n            title: _t(\"Create Passkey\"),\n            body: renderToMarkup(\"auth_passkey_portal.create\"),\n            confirmLabel: _t(\"Create\"),\n            confirm: async ({ inputEl }) => {\n                const name = inputEl.value;\n                if (name.length > 0) {\n                    this.createPasskey(serverOptions, name);\n                }\n            },\n            cancelLabel: _t(\"Discard\"),\n            cancel: () => {},\n        });\n    }\n\n    async createPasskey(serverOptions, name) {\n        const registration = await passkeyLib\n            .startRegistration(serverOptions)\n            .catch((e) => console.error(e));\n        const [new_key] = await this.services.orm.create(\"auth.passkey.key.create\", [{ name }]);\n        await handleCheckIdentity(\n            this.services.orm.call(\"auth.passkey.key.create\", \"make_key\", [\n                new_key,\n                registration,\n            ]),\n            this.services.orm,\n            this.services.dialog\n        );\n        location.reload();\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"auth_passkey_portal.create\", PortalPasskeyCreate);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { handleCheckIdentity } from \"@portal/interactions/portal_security\";\nimport { user } from \"@web/core/user\";\n\nexport class RevokeAllTrustedDevices extends Interaction {\n    static selector = \"#auth_totp_portal_revoke_all_devices\";\n    dynamicContent = {\n        _root: { \"t-on-click.prevent\": this.onClick },\n    };\n\n    async onClick() {\n        await this.waitFor(handleCheckIdentity(\n            this.waitFor(this.services.orm.call(\"res.users\", \"revoke_all_devices\", [user.userId])),\n            this.services.orm,\n            this.services.dialog,\n        ));\n        location.reload();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"auth_totp_portal.revoke_all_trusted_devices\", RevokeAllTrustedDevices);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { handleCheckIdentity } from \"@portal/interactions/portal_security\";\n\nexport class RevokeTrustedDevice extends Interaction {\n    static selector = \"#totp_wizard_view + * .fa.fa-trash.text-danger\";\n    dynamicContent = {\n        _root: { \"t-on-click.prevent\": this.onClick },\n    };\n\n    async onClick() {\n        await this.waitFor(handleCheckIdentity(\n            this.waitFor(this.services.orm.call(\"auth_totp.device\", \"remove\", [parseInt(this.el.id)])),\n            this.services.orm,\n            this.services.dialog,\n        ));\n        location.reload();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"auth_totp_portal.revoke_trusted_device\", RevokeTrustedDevice);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { handleCheckIdentity } from \"@portal/interactions/portal_security\";\nimport { user } from \"@web/core/user\";\n\nexport class TOTPDisable extends Interaction {\n    static selector = \"#auth_totp_portal_disable\";\n    dynamicContent = {\n        _root: { \"t-on-click.prevent\": this.onClick }\n    }\n\n    async onClick() {\n        await this.waitFor(handleCheckIdentity(\n            this.waitFor(this.services.orm.call(\"res.users\", \"action_totp_disable\", [user.userId])),\n            this.services.orm,\n            this.services.dialog,\n        ));\n        location.reload();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"auth_totp_portal.totp_disable\", TOTPDisable);\n\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { InputConfirmationDialog } from \"@portal/js/components/input_confirmation_dialog/input_confirmation_dialog\";\nimport { handleCheckIdentity } from \"@portal/interactions/portal_security\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { user } from \"@web/core/user\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nimport { markup } from \"@odoo/owl\";\n\n/**\n * Replaces specific <field> elements by normal HTML, strip out the rest entirely\n */\nfunction fromField(f, record) {\n    switch (f.getAttribute(\"name\")) {\n        case \"qrcode\":\n            const qrcode = document.createElement(\"img\");\n            qrcode.setAttribute(\"class\", \"img img-fluid\");\n            qrcode.setAttribute(\"src\", \"data:image/png;base64,\" + record[\"qrcode\"]);\n            return qrcode;\n        case \"url\":\n            const url = document.createElement(\"a\");\n            url.setAttribute(\"href\", record[\"url\"]);\n            url.textContent = f.getAttribute(\"text\") || record[\"url\"];\n            return url;\n        case \"code\":\n            const code = document.createElement(\"input\");\n            code.setAttribute(\"name\", \"code\");\n            code.setAttribute(\"class\", \"form-control col-10 col-md-6\");\n            code.setAttribute(\"placeholder\", \"6-digit code\");\n            code.required = true;\n            code.maxLength = 6;\n            code.minLength = 6;\n            return code;\n        case \"secret\":\n            // As CopyClipboard wizard is backend only, mimic his behaviour to use it in frontend.\n            // Field\n            const secretSpan = document.createElement(\"span\");\n            secretSpan.setAttribute(\"name\", \"secret\");\n            secretSpan.setAttribute(\"class\", \"o_field_copy_url\");\n            secretSpan.textContent = record[\"secret\"];\n\n            // Copy Button\n            const copySpanIcon = document.createElement(\"span\");\n            copySpanIcon.setAttribute(\"class\", \"fa fa-clipboard\");\n            const copySpanText = document.createElement(\"span\");\n            copySpanText.textContent = _t(\" Copy\");\n\n            const copyButton = document.createElement(\"button\");\n            copyButton.setAttribute(\"class\", \"btn btn-sm btn-primary o_clipboard_button o_btn_char_copy py-0 px-2\");\n            copyButton.onclick = async function (event) {\n                event.preventDefault();\n                $(copyButton).tooltip({ title: _t(\"Copied!\"), trigger: \"manual\", placement: \"bottom\" });\n                await browser.navigator.clipboard.writeText($(secretSpan)[0].innerText);\n                $(copyButton).tooltip(\"show\");\n                setTimeout(() => $(copyButton).tooltip(\"hide\"), 800);\n            };\n\n            copyButton.appendChild(copySpanIcon);\n            copyButton.appendChild(copySpanText);\n\n            // CopyClipboard Div\n            const secretDiv = document.createElement(\"div\");\n            secretDiv.setAttribute(\"class\", \"o_field_copy d-flex justify-content-center align-items-center\");\n            secretDiv.appendChild(secretSpan);\n            secretDiv.appendChild(copyButton);\n\n            return secretDiv;\n        default: // just display the field's data\n            return document.createTextNode(record[f.getAttribute(\"name\")] || \"\");\n    }\n}\n\n/**\n * Apparently chrome literally absolutely can't handle parsing XML and using\n * those nodes in an HTML document (even when parsing as application/xhtml+xml),\n * this results in broken rendering and a number of things not working (e.g.\n * classes) without any specific warning in the console or anything, things are\n * just broken with no indication of why.\n *\n * So... rebuild the entire f'ing body using document.createElement to ensure\n * we have HTML elements.\n *\n * This is a recursive implementation so it's not super efficient but the views\n * to fixup *should* be relatively simple.\n */\nfunction fixupViewBody(oldNode, record) {\n    let qrcode = null, code = null, node = null;\n\n    switch (oldNode.nodeType) {\n        case 1: // element\n            if (oldNode.tagName === \"field\") {\n                node = fromField(oldNode, record);\n                switch (oldNode.getAttribute(\"name\")) {\n                    case \"qrcode\":\n                        qrcode = node;\n                        break;\n                    case \"code\":\n                        code = node;\n                        break\n                }\n                break; // no need to recurse here\n            }\n            node = document.createElement(oldNode.tagName);\n            for (let i = 0; i < oldNode.attributes.length; ++i) {\n                const attr = oldNode.attributes[i];\n                node.setAttribute(attr.name, attr.value);\n            }\n            for (let j = 0; j < oldNode.childNodes.length; ++j) {\n                const [ch, qr, co] = fixupViewBody(oldNode.childNodes[j], record);\n                if (ch) { node.appendChild(ch); }\n                if (qr) { qrcode = qr; }\n                if (co) { code = co; }\n            }\n            break;\n        case 3: case 4: // text, cdata\n            node = document.createTextNode(oldNode.data);\n            break;\n        default:\n        // don't care about PI & al\n    }\n\n    return [node, qrcode, code]\n}\n\nexport class TOTPEnable extends Interaction {\n    static selector = \"#auth_totp_portal_enable\";\n    dynamicContent = {\n        _root: { \"t-on-click.prevent\": this.onClick },\n    };\n\n    async onClick() {\n        const data = await this.waitFor(handleCheckIdentity(\n            this.waitFor(this.services.orm.call(\"res.users\", \"action_totp_enable_wizard\", [user.userId])),\n            this.services.orm,\n            this.services.dialog,\n        ));\n\n        if (!data) {\n            // TOTP probably already enabled, just reload page\n            location.reload()\n            return;\n        }\n\n        const model = data.res_model;\n        const wizard_id = data.res_id;\n        const record = (await this.services.orm.read(model, [wizard_id], []))[0];\n\n        const doc = new DOMParser().parseFromString(\n            document.getElementById(\"totp_wizard_view\").textContent,\n            \"application/xhtml+xml\"\n        );\n\n        const xmlBody = doc.querySelector(\"sheet *\");\n        const [body, ,] = fixupViewBody(xmlBody, record);\n\n        this.services.dialog.add(InputConfirmationDialog, {\n            body: markup(body.outerHTML),\n            onInput: ({ inputEl }) => { inputEl.setCustomValidity(\"\") },\n            confirmLabel: _t(\"Activate\"),\n            confirm: async ({ inputEl }) => {\n                if (!inputEl.reportValidity()) {\n                    inputEl.classList.add(\"is-invalid\");\n                    return false;\n                }\n\n                try {\n                    await handleCheckIdentity(\n                        this.waitFor(this.services.orm.call(model, \"enable\",\n                            [ record.id ],\n                            { 'context': {'code': inputEl.value} },\n                        )),\n                        this.services.orm,\n                        this.services.dialog\n                    );\n                } catch (e) {\n                    const errorMessage = (\n                        !e.message ? e.toString()\n                            : !e.message.data ? e.message.message\n                                : e.message.data.message || _t(\"Operation failed for unknown reason.\")\n                    );\n                    inputEl.classList.add(\"is-invalid\");\n                    // show custom validity error message\n                    inputEl.setCustomValidity(errorMessage);\n                    inputEl.reportValidity();\n                    return false;\n                }\n                // reloads page, avoid window.location.reload() because it re-posts forms\n                location.reload();\n            },\n            cancel: () => { },\n        });\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"auth_totp_portal.totp_enable\", TOTPEnable);\n", "$(function() {\nif (odoo && odoo.__DEBUG__ === undefined) {\n    // skip if >= 17.0\n    return;\n}\n\nconst resend_activation_email_modal_frontend_deps = [\n    'web.ajax',\n    'web.core',\n    'web.session',\n    'web.Widget',\n];\nodoo.define('saas_trial.resend_activation_email_modal_frontend',\n            resend_activation_email_modal_frontend_deps,\n            function (require) {\n    'use strict';\n    var core = require('web.core');\n    var _t = core._t;\n    var ajax = require('web.ajax');\n    var session = require('web.session');\n    var Widget = require('web.Widget');\n\n    var ResendActivationEmailModal = Widget.extend({\n        template: 'SaasTrial.ResendEmailModal',\n        events: {\n            'submit form': '_send_email',\n        },\n        init: function(parent, usedEmail, email, expire_time) {\n            this._super(parent);\n            this.usedEmail = usedEmail;\n            this.email = email;\n            this.isAdmin = session.is_admin || session.is_system;\n            this.expire_time = expire_time;\n            this.time_left = moment().utc().from(expire_time, true);\n            this._refresh_time_left();\n        },\n        start: function() {\n            this.$el.find('.modal').modal('show');\n        },\n        _send_email: function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            var self = this;\n            var email = $(e.target).find('input[name=\"email\"]').val();\n            var send_btn = $(e.target).find('input[type=\"submit\"]');\n            send_btn.val(_t('Sending...'));\n            send_btn.prop('disabled', true);\n            var params = {\n                'activation_email': email,\n            };\n            ajax.jsonRpc('/saas_worker/send_activation_email', 'call', params).then(function(result) {\n                if(result.success) {\n                    self.$el.find('.modal-body').text(result.success);\n                }\n                else {\n                    self.$el.find('.modal-body').text(result.error);\n                }\n            });\n        },\n        _refresh_time_left: function() {\n            var self = this;\n            setTimeout(function() {\n                if (self.isDestroyed()) {\n                    return;\n                }\n                self.time_left = moment().utc().from(self.expire_time, true);\n                self._refresh_time_left();\n                self.$el.find('.oe_time_left').text(self.time_left);\n            }, 60000);\n        },\n    });\n    return ResendActivationEmailModal;\n});\nvar deps = [\n    'saas_trial.resend_activation_email_modal_frontend',\n    'saas_trial.trial_info_frontend',\n    'web.core',\n    'web.session',\n    'web.Widget',\n];\n// >= saas-11: web_enterprise.Menu was introduced in saas-11\n// if backend\nif (Object.prototype.hasOwnProperty.call(odoo.__DEBUG__.services, 'web_enterprise.Menu')) {\n    deps.push('web.SystrayMenu');\n}\nodoo.define('saas_trial.db_expiration_tag_frontend', deps, function (require) {\n    'use strict';\n    var core = require('web.core');\n    var Widget = require('web.Widget');\n    var ResendActivationEmailModal = require('saas_trial.resend_activation_email_modal_frontend');\n    var session = require('web.session');\n    const get_trial_info = require('saas_trial.trial_info_frontend');\n    var ExpirationTag = Widget.extend({\n        xmlDependencies: ['/saas_trial/static/xml/trial.xml'],\n        template: 'saas_trial.db_expiration_tag',\n        events: {\n            'click a': '_open_modal',\n        },\n        init: function(parent) {\n            this._super(parent);\n            core.bus.on('db_activation_requested', this, this._on_db_activation_requested);\n        },\n        willStart: async function() {\n            await this._super.apply(this, arguments);\n\n            const trial_info = await get_trial_info();\n            this.usedEmail = trial_info.activation_email;\n            this.email = trial_info.user_email;\n            this.expire_time = moment.utc(trial_info.expiry_oe || trial_info.expiry);\n            this.visible = trial_info.status === 'to_activate' && session.is_admin;\n        },\n        _open_modal: function(e) {\n            if (e) {\n                e.preventDefault();\n            }\n            var modal = new ResendActivationEmailModal(this, this.usedEmail, this.email, this.expire_time);\n            modal.appendTo($('body'));\n        },\n        _on_db_activation_requested: function() {\n            this._open_modal(undefined);\n        },\n    });\n    if (Object.prototype.hasOwnProperty.call(odoo.__DEBUG__.services, 'web_enterprise.Menu')) {\n        var SystrayMenu = require('web.SystrayMenu');\n        SystrayMenu.Items.push(ExpirationTag);\n    }\n    return ExpirationTag;\n});\nconst trial_info_frontend_deps = [\n    'web.session',\n];\nodoo.define('saas_trial.trial_info_frontend', trial_info_frontend_deps, function (require) {\n    'use strict';\n    var session = require('web.session');\n    let trial_info_promise = null;\n    async function get_trial_info() {\n        if (trial_info_promise === null) {\n            trial_info_promise = session.rpc('/saas_worker/trial_info', {});\n        }\n        return await trial_info_promise;\n    }\n    return get_trial_info;\n});\n});\n", "/* global Stripe */\n\nimport { patch } from '@web/core/utils/patch';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport { redirect } from '@web/core/utils/urls';\nimport { ExpressCheckout } from '@payment/interactions/express_checkout';\nimport { StripeOptions } from '@payment_stripe/interactions/stripe_options';\n\npatch(ExpressCheckout.prototype, {\n    /**\n     * Get the order details to display on the payment form.\n     *\n     * @private\n     * @param {number} deliveryAmount - The delivery costs.\n     * @param {number} amountFreeShipping - The free shipping discount amount, <= 0.\n     * @returns {Object} The information to be displayed on the payment form.\n     */\n    _getOrderDetails(deliveryAmount, amountFreeShipping) {\n        const pending = this.paymentContext['shippingInfoRequired'] && deliveryAmount === undefined;\n        const minorAmount = parseInt(this.paymentContext['minorAmount']);\n        const displayItems = [{\n            label: _t(\"Your order\"),\n            amount: minorAmount,\n        }];\n        if (this.paymentContext['shippingInfoRequired'] && deliveryAmount !== undefined) {\n            displayItems.push({\n                label: _t(\"Delivery\"),\n                amount: deliveryAmount,\n            });\n        }\n        if (amountFreeShipping) {\n            displayItems.push({\n                label: _t(\"Free Shipping\"),\n                amount: amountFreeShipping,\n            });\n        }\n        return {\n            total: {\n                label: this.paymentContext['merchantName'],\n                amount: minorAmount + (deliveryAmount ?? 0) + (amountFreeShipping ?? 0),\n                // Delay the display of the amount until the shipping price is retrieved.\n                pending: pending,\n            },\n            displayItems: displayItems,\n        };\n    },\n\n    /**\n     * Prepare the express checkout form of Stripe for direct payment.\n     *\n     * @override method from payment.express_form\n     * @private\n     * @param {Object} providerData - The provider-specific data.\n     * @return {void}\n     */\n    async _prepareExpressCheckoutForm(providerData) {\n        /*\n         * When applying a coupon, the amount can be totally covered, with nothing left to pay. In\n         * that case, the check is whether the variable is defined because the server doesn't send\n         * the value when it equals '0'.\n         */\n        if (providerData.providerCode !== 'stripe' || !this.paymentContext['amount']) {\n            super._prepareExpressCheckoutForm(...arguments);\n            return;\n        }\n\n        const stripeJS = Stripe(\n            providerData.stripePublishableKey,\n            new StripeOptions()._prepareStripeOptions(providerData),\n        );\n        const paymentRequest = stripeJS.paymentRequest({\n            country: providerData.countryCode,\n            currency: this.paymentContext['currencyName'],\n            requestPayerName: true, // Force fetching the billing address for Apple Pay.\n            requestPayerEmail: true,\n            requestPayerPhone: true,\n            requestShipping: this.paymentContext['shippingInfoRequired'],\n            ...this._getOrderDetails(),\n        });\n        if (this.stripePaymentRequests === undefined) {\n            this.stripePaymentRequests = [];\n        }\n        this.stripePaymentRequests.push(paymentRequest);\n        const paymentRequestButton = stripeJS.elements().create('paymentRequestButton', {\n            paymentRequest: paymentRequest,\n            style: {paymentRequestButton: {type: 'buy'}},\n        });\n\n        // Check the availability of the Payment Request API first.\n        const canMakePayment = await this.waitFor(paymentRequest.canMakePayment());\n        if (canMakePayment) {\n            paymentRequestButton.mount(\n                `#o_stripe_express_checkout_container_${providerData.providerId}`\n            );\n        } else {\n            document.querySelector(\n                `#o_stripe_express_checkout_container_${providerData.providerId}`\n            ).style.display = 'none';\n        }\n\n        paymentRequest.on('paymentmethod', async (ev) => {\n            const addresses = {\n                'billing_address': {\n                    name: ev.payerName,\n                    email: ev.payerEmail,\n                    phone: ev.payerPhone,\n                    street: ev.paymentMethod.billing_details.address.line1,\n                    street2: ev.paymentMethod.billing_details.address.line2,\n                    zip: ev.paymentMethod.billing_details.address.postal_code,\n                    city: ev.paymentMethod.billing_details.address.city,\n                    country: ev.paymentMethod.billing_details.address.country,\n                    state: ev.paymentMethod.billing_details.address.state,\n                }\n            };\n            if (this.paymentContext['shippingInfoRequired']) {\n                addresses.shipping_address = {\n                    name: ev.shippingAddress.recipient,\n                    email: ev.payerEmail,\n                    phone: ev.shippingAddress.phone || ev.payerPhone,\n                    street: ev.shippingAddress.addressLine[0],\n                    street2: ev.shippingAddress.addressLine[1],\n                    zip: ev.shippingAddress.postalCode,\n                    city: ev.shippingAddress.city,\n                    country: ev.shippingAddress.country,\n                    state: ev.shippingAddress.region,\n                };\n                addresses.shipping_option = ev.shippingOption;\n            }\n            // Update the customer addresses on the related document.\n            this.paymentContext.partnerId = parseInt(await this.waitFor(rpc(\n                this.paymentContext['expressCheckoutRoute'],\n                addresses,\n            )));\n            // Call the transaction route to create the transaction and retrieve the client secret.\n            const { client_secret } = await this.waitFor(rpc(\n                this.paymentContext['transactionRoute'],\n                this._prepareTransactionRouteParams(providerData.providerId),\n            ));\n            // Confirm the PaymentIntent without handling eventual next actions (e.g. 3DS).\n            const { paymentIntent, error: confirmError } = await this.waitFor(\n                stripeJS.confirmCardPayment(\n                    client_secret, {payment_method: ev.paymentMethod.id}, {handleActions: false}\n                )\n            );\n            if (confirmError) {\n                // Report to the browser that the payment failed, prompting it to re-show the\n                // payment interface, or show an error message and close the payment interface.\n                ev.complete('fail');\n            } else {\n                // Report to the browser that the confirmation was successful, prompting it to close\n                // the browser payment method collection interface.\n                ev.complete('success');\n                if (paymentIntent.status === 'requires_action') { // A next step is required.\n                    // Trigger the step.\n                    await this.waitFor(stripeJS.confirmCardPayment(client_secret));\n                }\n                redirect('/payment/status');\n            }\n        });\n\n        if (this.paymentContext['shippingInfoRequired']) {\n            // Wait until the express checkout form is loaded for Apple Pay and Google Pay to select\n            // a default shipping address and trigger the `shippingaddresschange` event, so we can\n            // fetch the available shipping options. When the customer manually selects a different\n            // shipping address, the shipping options need to be fetched again.\n            paymentRequest.on('shippingaddresschange', async (ev) => {\n                // Call the shipping address update route to fetch the shipping options.\n                const availableCarriersData = await this.waitFor(rpc(\n                    this.paymentContext['shippingAddressUpdateRoute'],\n                    {\n                        partial_delivery_address: {\n                            zip: ev.shippingAddress.postalCode,\n                            city: ev.shippingAddress.city,\n                            country: ev.shippingAddress.country,\n                            state: ev.shippingAddress.region,\n                        },\n                    },\n                ));\n                this.paymentContext['minorAmount'] = await this.waitFor(rpc(\n                    this.paymentContext['shippingAddressUpdateRoute'] + '/compute_taxes',\n                ));\n                const { delivery_methods, delivery_discount_minor_amount } = availableCarriersData;\n                if (delivery_methods.length === 0) {\n                    ev.updateWith({status: 'invalid_shipping_address'});\n                } else {\n                    ev.updateWith({\n                        status: 'success',\n                        shippingOptions: delivery_methods.map(carrier => ({\n                            id: String(carrier.id),\n                            label: carrier.name,\n                            detail: carrier.description ? carrier.description:'',\n                            amount: carrier.minorAmount,\n                        })),\n                        ...this._getOrderDetails(\n                            delivery_methods[0].minorAmount,\n                            delivery_discount_minor_amount,\n                        ),\n                    });\n                }\n            });\n\n            // When the customer selects a different shipping option, update the displayed total.\n            paymentRequest.on('shippingoptionchange', async (ev) => {\n                const result = await this.waitFor(rpc('/shop/set_delivery_method', {\n                    dm_id: parseInt(ev.shippingOption.id),\n                }));\n                ev.updateWith({\n                    status: 'success',\n                    ...this._getOrderDetails(\n                        ev.shippingOption.amount,\n                        parseInt(result.delivery_discount_minor_amount) || 0,\n                    ),\n                });\n            });\n        }\n    },\n\n    /**\n     * Update the amount of the express checkout form.\n     *\n     * @override method from payment.express_form\n     * @private\n     * @param {number} newAmount - The new amount.\n     * @param {number} newMinorAmount - The new minor amount.\n     * @return {void}\n     */\n    _updateAmount(newAmount, newMinorAmount) {\n        super._updateAmount(...arguments);\n        this.stripePaymentRequests && this.stripePaymentRequests.map(\n            paymentRequest => paymentRequest.update(this._getOrderDetails())\n        );\n    },\n});\n", "/* global Stripe */\n\nimport { StripeOptions } from '@payment_stripe/interactions/stripe_options';\nimport { _t } from '@web/core/l10n/translation';\nimport { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n\n    setup() {\n        super.setup();\n        this.stripeElements = {}; // Store the element of each instantiated payment method.\n    },\n\n    // #=== DOM MANIPULATION ===#\n\n    /**\n     * Prepare the inline form of Stripe for direct payment.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {number} providerId - The id of the selected payment option's provider.\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The online payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (providerCode !== 'stripe') {\n            await super._prepareInlineForm(...arguments);\n            return;\n        }\n\n        // Check if instantiation of the element is needed.\n        if (flow === 'token') {\n            return; // No elements for tokens.\n        } else if (this.stripeElements[paymentOptionId]) {\n            this._setPaymentFlow('direct'); // Overwrite the flow even if no re-instantiation.\n            return; // Don't re-instantiate if already done for this provider.\n        }\n\n        // Overwrite the flow of the select payment option.\n        this._setPaymentFlow('direct');\n\n        // Extract and deserialize the inline form values.\n        const radio = document.querySelector('input[name=\"o_payment_radio\"]:checked');\n        const inlineForm = this._getInlineForm(radio);\n        const stripeInlineForm = inlineForm.querySelector('[name=\"o_stripe_element_container\"]');\n        this.stripeInlineFormValues = JSON.parse(\n            stripeInlineForm.dataset['stripeInlineFormValues']\n        );\n\n        // Instantiate Stripe object if needed.\n        this.stripeJS ??= Stripe(\n            this.stripeInlineFormValues['publishable_key'],\n            // The values required by Stripe Connect are inserted into the dataset.\n            new StripeOptions()._prepareStripeOptions(stripeInlineForm.dataset),\n        );\n\n        // Instantiate the elements.\n        let elementsOptions =  {\n            appearance: { theme: 'stripe' },\n            currency: this.stripeInlineFormValues['currency_name'],\n            captureMethod: this.stripeInlineFormValues['capture_method'],\n            paymentMethodTypes: [\n                this.stripeInlineFormValues['payment_methods_mapping'][paymentMethodCode]\n                ?? paymentMethodCode\n            ],\n        };\n        if (this.paymentContext['mode'] === 'payment') {\n            elementsOptions.mode = 'payment';\n            elementsOptions.amount = parseInt(this.stripeInlineFormValues['minor_amount']);\n            if (this.stripeInlineFormValues['is_tokenization_required']) {\n                elementsOptions.setupFutureUsage = 'off_session';\n            }\n        }\n        else {\n            elementsOptions.mode = 'setup';\n            elementsOptions.setupFutureUsage = 'off_session';\n        }\n        this.stripeElements[paymentOptionId] = this.stripeJS.elements(elementsOptions);\n\n        // Instantiate the payment element.\n        const paymentElementOptions = {\n            defaultValues: {\n                billingDetails: this.stripeInlineFormValues['billing_details'],\n            },\n        };\n        const paymentElement = this.stripeElements[paymentOptionId].create(\n            'payment', paymentElementOptions\n        );\n        paymentElement.on('loaderror', response => {\n            this._displayErrorDialog(_t(\"Cannot display the payment form\"), response.error.message);\n        });\n        paymentElement.mount(stripeInlineForm);\n\n        const tokenizationCheckbox = inlineForm.querySelector(\n            'input[name=\"o_payment_tokenize_checkbox\"]'\n        );\n        if (tokenizationCheckbox) {\n            // Display tokenization-specific inputs when the tokenization checkbox is checked.\n            this.stripeElements[paymentOptionId].update({\n                setupFutureUsage: tokenizationCheckbox.checked ? 'off_session' : null,\n            }); // Force sync the states of the API and the checkbox in case they were inconsistent.\n            tokenizationCheckbox.addEventListener('input', () => {\n                this.stripeElements[paymentOptionId].update({\n                    setupFutureUsage: tokenizationCheckbox.checked ? 'off_session' : null,\n                });\n            });\n        }\n    },\n\n    // #=== PAYMENT FLOW ===#\n\n    /**\n     * Trigger the payment processing by submitting the elements.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _initiatePaymentFlow(providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (providerCode !== 'stripe' || flow === 'token') {\n            // Tokens are handled by the generic flow.\n            await super._initiatePaymentFlow(...arguments);\n            return;\n        }\n\n        // Trigger form validation and wallet collection.\n        try {\n            await this.waitFor(this.stripeElements[paymentOptionId].submit());\n        } catch (error) {\n            this._displayErrorDialog(_t(\"Incorrect payment details\"), error.message);\n            this._enableButton();\n            return;\n        }\n        await super._initiatePaymentFlow(...arguments);\n    },\n\n    /**\n     * Process Stripe implementation of the direct payment flow.\n     *\n     * @override method from payment.payment_form\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n    async _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {\n        if (providerCode !== 'stripe') {\n            await super._processDirectFlow(...arguments);\n            return;\n        }\n\n        const { error } = await this.waitFor(\n            this._stripeConfirmIntent(processingValues, paymentOptionId)\n        );\n        if (error) {\n            this._displayErrorDialog(_t(\"Payment processing failed\"), error.message);\n            this._enableButton();\n        }\n    },\n\n    /**\n     * Confirm the intent on Stripe's side and handle any next action.\n     *\n     * @private\n     * @param {object} processingValues - The processing values of the transaction.\n     * @param {number} paymentOptionId - The id of the payment option handling the transaction.\n     * @return {object} The processing error, if any.\n     */\n    async _stripeConfirmIntent(processingValues, paymentOptionId) {\n        const confirmOptions = {\n            elements: this.stripeElements[paymentOptionId],\n            clientSecret: processingValues['client_secret'],\n            confirmParams: {\n                return_url: processingValues['return_url'],\n            },\n        };\n        if (this.paymentContext['mode'] === 'payment'){\n             return await this.stripeJS.confirmPayment(confirmOptions);\n        }\n        else {\n            return await this.stripeJS.confirmSetup(confirmOptions);\n        }\n    },\n\n});\n", "\nexport class StripeOptions {\n    /**\n     * Prepare the options to init the Stripe JS Object.\n     *\n     * This method serves as a hook for modules that would fully implement Stripe Connect.\n     *\n     * @param {object} processingValues\n     * @return {object}\n     */\n    _prepareStripeOptions(processingValues) {\n        const locale = document.documentElement.lang;\n        return {\n            'apiVersion': '2019-05-16',  // The API version of Stripe implemented in this module.\n            ...(locale ? { locale } : {}),  // Default to browser locale if not set.\n        };\n    };\n}\n", "(function () {\n\n    const stripeMixin = {\n\n        /**\n         * Prepare the options to init the Stripe JS Object\n         *\n         * @param {object} processingValues\n         * @return {object}\n         */\n        _prepareStripeOptions: function (processingValues) {\n            var res;\n            res = this._super.apply(this, arguments);\n            res.stripeAccount = processingValues.stripe_account;\n            return res;\n        },\n\n    };\n\n    // This has no effect in >= 16.0 because _processRedirectPayment calls StripeOptions'\n    // _prepareStripeOptions method, and not the forms' method. Starting with 17.0, we can discriminate\n    // the version and avoid including the mixin for nothing.\n    if (!odoo.loader) { // implies < 17.0\n        const checkoutFormPath = 'payment.checkout_form';\n        const manageFormPath = 'payment.manage_form';\n        odoo.define('saas_payment_stripe.PaymentForms', [checkoutFormPath, manageFormPath], function (require) {\n            const checkoutForm = require(checkoutFormPath);\n            const manageForm = require(manageFormPath);\n            checkoutForm.include(stripeMixin);\n            manageForm.include(stripeMixin);\n        });\n    }\n})();\n", "(function () {\n\nfunction patchCompat(patchFn, target, name, patch) {\n    if (odoo.loader ) { // implies 17.0+\n        return patchFn(target, patch);\n    } else {\n        return patchFn(target, name, patch);\n    }\n}\n\nconst patchPath = '@web/core/utils/patch';\nconst stripeOptionsPath =\n    // odoo's JS modules are loaded following topological order, so simply determine version\n    // depending of payment_stripe loaded module.\n    odoo.loader && odoo.loader.modules.get('@payment_stripe/interactions/stripe_options')\n    ? '@payment_stripe/interactions/stripe_options'  // >= saas~18.5\n    : '@payment_stripe/js/stripe_options'; // <= saas~18.4\n\nodoo.define('saas_payment_stripe.StripeOptions', [patchPath, stripeOptionsPath], function (require) {\n    'use strict';\n\n    const { patch } = require(patchPath);\n    const { StripeOptions } = require(stripeOptionsPath);\n\n    patchCompat(patch, StripeOptions.prototype, 'stripe_connect', {\n\n        /**\n         * Override of `payment_stripe` to add the account id to the Stripe JS options.\n         *\n         * @param {object} processingValues\n         * @return {object}\n         */\n        _prepareStripeOptions(processingValues) {\n\n            let res;\n            if (this._super) {\n                res = this._super(processingValues);\n            } else {\n                res = super._prepareStripeOptions(processingValues);\n            }\n            res.stripeAccount = processingValues.stripe_account;\n            return res;\n        },\n    });\n\n});\n})();\n", "import { rpc } from \"@web/core/network/rpc\";\n\nfunction makeGooglePlacesSession() {\n    let current;\n\n    /**\n     * Used to generate a unique session ID for the places API.\n     * According to the API docs:\n     * \"The session begins when the user starts typing a query,\n     * and concludes when they select a place and a call to Place Details is made.\n     * Each session can have multiple queries, followed by one place selection.\n     * [...] Once a session has concluded, the token is no longer valid;\n     * your app must generate a fresh token for each session.\"\n     * https://developers.google.com/maps/documentation/places/web-service/details#session_tokens\n     */\n    function generateUUID() {\n        return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n            const r = (Math.random() * 16) | 0,\n                v = c == \"x\" ? r : (r & 0x3) | 0x8;\n            return v.toString(16);\n        });\n    }\n\n    function getAddressPropositions(params = {}) {\n        if (!params.session_id) {\n            current = current || generateUUID();\n            params.session_id = current;\n        }\n        return rpc(\"/autocomplete/address\", params);\n    }\n\n    async function getAddressDetails(params = {}) {\n        if (!params.session_id) {\n            current = current || generateUUID();\n            params.session_id = current;\n        }\n        current = null;\n        return rpc(\"/autocomplete/address_full\", params);\n    }\n\n    return {\n        get sessionToken() {\n            return current;\n        },\n        getAddressPropositions,\n        getAddressDetails,\n    };\n}\n\nexport const googlePlacesSession = makeGooglePlacesSession();\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { googlePlacesSession } from \"@google_address_autocomplete/google_places_session\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\n\nexport class AddressForm extends Interaction {\n    static selector = \".oe_cart .address_autoformat\";\n    static selectorHas = \"input[name='street'][data-autocomplete-enabled='1']\";\n    dynamicContent = {\n        \"input[name='street']\": { \"t-on-input.withTarget\": this.debounced(this.onStreetInput, 200) },\n        \".js_autocomplete_result\": { \"t-on-click.withTarget\": this.onClickAutocompleteResult },\n    };\n\n    setup() {\n        this.streetAndNumberInput = this.el.querySelector(\"input[name='street']\");\n        this.cityInput = this.el.querySelector(\"input[name='city']\");\n        this.zipInput = this.el.querySelector(\"input[name='zip']\");\n        this.countrySelect = this.el.querySelector(\"select[name='country_id']\");\n        this.stateSelect = this.el.querySelector(\"select[name='state_id']\");\n        this.keepLast = new KeepLast();\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onStreetInput(ev, inputEl) {\n        const inputContainerEl = inputEl.parentNode;\n        if (inputEl.value.length >= 5) {\n            this.keepLast.add(\n                googlePlacesSession.getAddressPropositions({\n                    partial_address: inputEl.value,\n                }).then((response) => {\n                    inputContainerEl.querySelector(\".dropdown-menu\")?.remove();\n                    this.renderAt(\"website_sale_autocomplete.AutocompleteDropDown\", {\n                        results: response.results,\n                    }, inputContainerEl);\n                })\n            );\n        } else {\n            inputContainerEl.querySelector(\".dropdown-menu\")?.remove();\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onClickAutocompleteResult(ev, currentTargetEl) {\n        const dropdownEl = currentTargetEl.parentNode;\n        dropdownEl.innerText = \"\";\n        dropdownEl.classList.add(\"d-flex\", \"justify-content-center\", \"align-items-center\");\n\n        const spinnerEl = document.createElement(\"div\");\n        spinnerEl.classList.add(\"spinner-border\", \"text-warning\", \"text-center\", \"m-auto\");\n        dropdownEl.appendChild(spinnerEl);\n\n        const address = await this.waitFor(googlePlacesSession.getAddressDetails({\n            address: currentTargetEl.innerText,\n            google_place_id: currentTargetEl.dataset.googlePlaceId,\n        }));\n\n        if (address.formatted_street_number) {\n            this.streetAndNumberInput.value = address.formatted_street_number;\n        }\n        // Text fields, empty if no value in order to avoid the user missing old data.\n        this.zipInput.value = address.zip || \"\";\n        this.cityInput.value = address.city || \"\";\n\n        // Selects based on odoo ids\n        if (address.country) {\n            this.countrySelect.value = address.country[0];\n            // Let the state select know that the country has changed so that it may fetch the correct states or disappear.\n            this.countrySelect.dispatchEvent(new Event(\"change\", { bubbles: true }));\n        }\n        if (address.state) {\n            // Waits for the stateSelect to update before setting the state.\n            new MutationObserver((entries, observer) => {\n                this.stateSelect.value = address.state[0];\n                observer.disconnect();\n            }).observe(this.stateSelect, {\n                childList: true, // Trigger only if the options change\n            });\n        }\n        dropdownEl.remove();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_sale_autocomplete.address_form\", AddressForm);\n", "import { parseSearchQuery, PATH_KEYS } from \"@web/core/browser/router\";\nimport { omit } from \"@web/core/utils/objects\";\nimport { isNumeric } from \"@web/core/utils/strings\";\nimport { objectToUrlEncodedString } from \"@web/core/utils/urls\";\n\n// Prefixes should be sorted by desc. length.\nexport const PREFIXES = [\"/knowledge/article\", \"/knowledge/home\"];\n\n/**\n * @param {{ [key: string]: any }} state\n * @returns {string}\n */\nexport function stateToUrl(state) {\n    let pathname;\n    if (!state.resId) {\n        pathname = \"/knowledge/home\";\n    } else {\n        pathname = `/knowledge/article/${state.resId}`;\n    }\n    const search = objectToUrlEncodedString(omit(state, \"actionStack\", ...PATH_KEYS));\n    return `${pathname}${search ? `?${search}` : \"\"}`;\n}\n\n/**\n * @param {URL} urlObj\n * @returns {{ [key: string]: any }}\n */\nexport function urlToState(urlObj) {\n    const { pathname, search } = urlObj;\n    const state = parseSearchQuery(search);\n    const prefix = PREFIXES.find((prefix) => pathname.startsWith(prefix));\n    if (prefix === \"/knowledge/article\") {\n        const splitPath = pathname.replace(prefix, \"\").split(\"/\").toSpliced(0, 1);\n        if (isNumeric(splitPath.at(0))) {\n            state.resId = parseInt(splitPath.at(0));\n        }\n    }\n    return state;\n}\n", "import { Dialog } from \"@web/core/dialog/dialog\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { closestScrollableY } from \"@web/core/utils/scrolling\";\nimport { highlightText } from \"@web/core/utils/html\";\nimport { debounce } from \"@web/core/utils/timing\";\nimport { Component, markup, onWillStart, useExternalListener, useRef, useState } from \"@odoo/owl\";\n\nexport class ArticleSearchDialog extends Component {\n    static template = \"knowledge.ArticleSearchDialog\";\n    static components = { Dialog };\n    static props = {\n        close: Function,\n        select: Function,\n        search: Function,\n        create: { type: Function, optional: true },\n        searchEmptyQuery: { type: Boolean, optional: true },\n    };\n\n    setup() {\n        this.state = useState({\n            articles: [],\n            searchValue: \"\",\n            selectedIdx: 0,\n            displayEmptySearch: false,\n        });\n        this.root = useRef(\"root\");\n        this.searchInput = useRef(\"searchInput\");\n        this.debouncedSearch = debounce(this.search, 500);\n        useExternalListener(window, \"pointerup\", this.onWindowPointerUp);\n        useHotkey(\"ArrowDown\", () => this.onArrowDown(), {\n            allowRepeat: true,\n            bypassEditableProtection: true,\n        });\n        useHotkey(\"ArrowUp\", () => this.onArrowUp(), {\n            allowRepeat: true,\n            bypassEditableProtection: true,\n        });\n        useHotkey(\"Enter\", () => this.openSelectedArticle(), { bypassEditableProtection: true });\n        useHotkey(\"escape\", () => this.props.close());\n        onWillStart(async () => {\n            if (this.props.searchEmptyQuery) {\n                await this.search(\"\");\n            }\n        });\n    }\n\n    onArrowUp() {\n        this.state.selectedIdx =\n            this.state.selectedIdx > 0\n                ? this.state.selectedIdx - 1\n                : this.state.articles.length - 1;\n    }\n\n    onArrowDown() {\n        this.state.selectedIdx =\n            this.state.selectedIdx < this.state.articles.length - 1\n                ? this.state.selectedIdx + 1\n                : 0;\n    }\n\n    onArticleClick(articleIdx) {\n        this.props.select(this.state.articles[articleIdx]);\n        this.props.close();\n    }\n\n    onArticleMouseEnter(articleIdx) {\n        this.state.selectedIdx = articleIdx;\n    }\n\n    onCreateClick() {\n        this.props.create(this.searchInput.el.value);\n        this.props.close();\n    }\n\n    async onSearchInput(ev) {\n        await this.debouncedSearch(ev.target.value);\n        this.state.displayEmptySearch = true;\n    }\n\n    onWindowPointerUp(ev) {\n        const container = closestScrollableY(this.root.el) ?? this.root.el;\n        if (!container.contains(ev.target)) {\n            this.props.close();\n        }\n    }\n\n    openSelectedArticle() {\n        if (this.state.articles.length > this.state.selectedIdx) {\n            this.props.select(this.state.articles[this.state.selectedIdx]);\n        }\n        this.props.close();\n    }\n\n    async search(searchValue) {\n        if (!searchValue.length && !this.props.searchEmptyQuery) {\n            this.state.articles = [];\n        } else {\n            this.state.articles = (await this.props.search(searchValue)).map((article) => ({\n                displayName: `${article.icon || \"\ud83d\udcc4\"} ${article.name}`.trim(),\n                headline: article.headline ? markup(article.headline) : \"\",\n                icon: article.icon || \"\ud83d\udcc4\",\n                id: article.id,\n                isFavorite: article.is_user_favorite,\n                text:\n                    article.name &&\n                    highlightText(searchValue, article.name, \"fw-bolder text-primary\"),\n                subjectText:\n                    article.root_article_id?.[0] &&\n                    article.root_article_id[0] != article.id &&\n                    highlightText(\n                        searchValue,\n                        article.root_article_id[1],\n                        \"fw-bolder text-primary\"\n                    ),\n            }));\n        }\n        this.state.selectedIdx = 0;\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { getEmbeddedProps } from \"@html_editor/others/embedded_component_utils\";\n\nexport class ReadonlyEmbeddedArticleIndexComponent extends Component {\n    static props = {\n        articles: { type: Object, optional: true },\n        showAllChildren: { type: Boolean, optional: true },\n    };\n    static defaultProps = {\n        articles: {},\n        showAllChildren: true,\n    };\n    static template = \"knowledge.ReadonlyEmbeddedArticleIndex\";\n\n    /** @param {integer} articleId */\n    openArticle(articleId) {\n        if (this.env.openArticle) {\n            this.env.openArticle(articleId);\n        }\n    }\n}\n\nexport const readonlyArticleIndexEmbedding = {\n    name: \"articleIndex\",\n    Component: ReadonlyEmbeddedArticleIndexComponent,\n    getProps: (host) => {\n        return {\n            ...getEmbeddedProps(host),\n        };\n    },\n};\n", "import {\n    getEditableDescendants,\n    useEditableDescendants,\n} from \"@html_editor/others/embedded_component_utils\";\nimport {\n    EmbeddedComponentToolbar,\n    EmbeddedComponentToolbarButton,\n} from \"@html_editor/others/embedded_components/core/embedded_component_toolbar/embedded_component_toolbar\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Tooltip } from \"@web/core/tooltip/tooltip\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\nimport { Component } from \"@odoo/owl\";\n\nexport class EmbeddedClipboardComponent extends Component {\n    static components = {\n        EmbeddedComponentToolbar,\n        EmbeddedComponentToolbarButton,\n    };\n    static props = {\n        host: { type: Object },\n    };\n    static template = \"knowledge.EmbeddedClipboard\";\n\n    setup() {\n        this.popover = usePopover(Tooltip);\n        this.descendants = useEditableDescendants(this.props.host);\n        this.copyToClipboardButtonRef = useChildRef();\n    }\n\n    //--------------------------------------------------------------------------\n    // HANDLERS\n    //--------------------------------------------------------------------------\n\n    async onClickCopyToClipboard() {\n        const selection = document.getSelection();\n        selection.removeAllRanges();\n        const range = new Range();\n        range.selectNodeContents(this.descendants.clipboardContent);\n        selection.addRange(range);\n        if (document.execCommand(\"copy\")) {\n            // Nor the original `clipboard.write` function nor the polyfill\n            // written in `clipboard.js` does trigger the `clipboard_plugin`\n            // `copy` handler, therefore `execCommand` should be called here so\n            // that html content is properly handled within the editor.\n            this.popover.open(this.copyToClipboardButtonRef.el, {\n                tooltip: _t(\"Content copied to clipboard.\"),\n            });\n            browser.setTimeout(this.popover.close, 800);\n        }\n        selection.removeAllRanges();\n    }\n}\n\nexport const clipboardEmbedding = {\n    name: \"clipboard\",\n    Component: EmbeddedClipboardComponent,\n    getEditableDescendants: getEditableDescendants,\n    getProps: (host) => {\n        return { host };\n    },\n};\n", "import { _t } from \"@web/core/l10n/translation\";\n\nexport const EMBEDDED_VIEW_LINK_STYLES = {\n    link: { display: _t(\"Link\"), class: \"btn btn-link\" },\n    primary: { display: _t(\"Primary\"), class: \"btn btn-primary\" },\n    secondary: { display: _t(\"Secondary\"), class: \"btn btn-secondary\" },\n};\n", "import {\n    getEditableDescendants,\n    getEmbeddedProps,\n    useEditableDescendants,\n} from \"@html_editor/others/embedded_component_utils\";\nimport { Component, useState } from \"@odoo/owl\";\n\nexport class ReadonlyFoldableSection extends Component {\n    static template = \"knowledge.ReadonlyEmbeddedFoldableSection\";\n    static props = {\n        host: { type: Object },\n        showContent: { type: Boolean, optional: true },\n    };\n    setup() {\n        this.editableDescendants = useEditableDescendants(this.props.host);\n        this.state = useState({\n            showContent: this.props.showContent,\n        });\n    }\n    onInputChange(ev) {\n        this.state.showContent = ev.target.checked;\n    }\n}\n\nexport const readonlyFoldableSectionEmbedding = {\n    name: \"foldableSection\",\n    Component: ReadonlyFoldableSection,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n    getEditableDescendants: getEditableDescendants,\n};\n", "import { registry } from \"@web/core/registry\";\n\n// See `HtmlUpgradeManager` docstring for usage details.\nconst html_upgrade = registry.category(\"html_editor_upgrade\");\n\n// Handle the conversion of `o_knowledge_behavior_anchor` elements to their\n// `data-embedded` counterpart, when loading the value of a html_field.\nhtml_upgrade.category(\"1.0\").add(\"knowledge\", \"@knowledge/editor/html_migrations/migration-1.0\");\n\n// embeddedViews favorite irFilters should have a `user_ids` property\nhtml_upgrade.category(\"2.0\").add(\"knowledge\", \"@knowledge/editor/html_migrations/migration-2.0\");\n", "import { decodeDataBehaviorProps, getPropNameNode } from \"@knowledge/editor/html_migrations/utils\";\nimport { getOrigin } from \"@web/core/utils/urls\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport function migrate(container, env) {\n    for (const [key, selector] of Object.entries(selectors)) {\n        const elements = container.querySelectorAll(selector);\n        if (elements.length) {\n            migrations[key](elements, env);\n        }\n    }\n}\n\nfunction migrateSearchModelState(searchModelState) {\n    searchModelState = JSON.parse(searchModelState);\n    const dfOptMap = {\n        this_year: \"year\",\n        last_year: \"year-1\",\n        antepenultimate_year: \"year-2\",\n        this_month: \"month\",\n        last_month: \"month-1\",\n        antepenultimate_month: \"month-2\",\n    };\n    for (const searchItem of Object.values(searchModelState.searchItems)) {\n        if (searchItem.type === \"dateFilter\") {\n            const newDefaults = new Set();\n            for (const generatorId of searchItem.defaultGeneratorIds) {\n                if (generatorId in dfOptMap) {\n                    newDefaults.add(dfOptMap[generatorId]);\n                }\n            }\n            if (newDefaults.size) {\n                searchItem.defaultGeneratorIds = Array.from(newDefaults);\n            }\n            if (!searchItem.optionsParams) {\n                searchItem.optionsParams = {\n                    startYear: -2,\n                    endYear: 0,\n                    startMonth: -2,\n                    endMonth: 0,\n                    customOptions: [],\n                };\n            }\n            for (const queryItem of searchModelState.query) {\n                if (\n                    queryItem.searchItemId === searchItem.id &&\n                    queryItem.generatorId &&\n                    queryItem.generatorId in dfOptMap\n                ) {\n                    queryItem.generatorId = dfOptMap[queryItem.generatorId];\n                }\n            }\n        }\n    }\n    return JSON.stringify(searchModelState);\n}\n\nconst selectors = {\n    articleBehavior: \".o_knowledge_behavior_type_article\",\n    articlesStructureBehavior: \".o_knowledge_behavior_type_articles_structure\",\n    comments: \".knowledge-thread-comment\",\n    drawBehavior: \".o_knowledge_behavior_type_draw\",\n    embeddedViewBehavior: \".o_knowledge_behavior_type_embedded_view\",\n    fileBehavior: \".o_knowledge_behavior_type_file\",\n    tableOfContentBehavior: \".o_knowledge_behavior_type_toc\",\n    templateBehavior: \".o_knowledge_behavior_type_template\",\n    videoBehavior: \".o_knowledge_behavior_type_video\",\n    viewLinkBehavior: \".o_knowledge_behavior_type_view_link\",\n};\n\nconst migrations = {\n    articleBehavior: (elements) => {\n        for (const el of elements) {\n            const oldProps = decodeDataBehaviorProps(el.dataset.behaviorProps);\n            if (!oldProps?.article_id || !oldProps?.display_name) {\n                // Abort the conversion if data can not be recovered.\n                // Element will still exist in the DOM as raw data.\n                continue;\n            }\n            delete el.dataset.behaviorProps;\n            el.dataset.res_id = oldProps?.article_id;\n            el.removeAttribute(\"tabindex\");\n            el.classList.remove(\"o_knowledge_behavior_anchor\", \"o_knowledge_behavior_type_article\");\n            el.classList.add(\"o_knowledge_article_link\");\n            el.replaceChildren(document.createTextNode(oldProps.display_name));\n        }\n    },\n    articlesStructureBehavior: (elements) => {\n        function buildIndex(el, props) {\n            const index = [];\n            while (el) {\n                const anchor = el.querySelector(\"a\");\n                const id = parseInt(\n                    anchor\n                        .getAttribute(\"href\")\n                        .match(/(\\d+)$/)\n                        .at(1)\n                );\n                const name = anchor.textContent;\n                const article = { id, name, childIds: [] };\n                el = el.nextElementSibling;\n                const child = el?.querySelector(\":scope > ol > li\");\n                if (child) {\n                    article.childIds = buildIndex(child, props);\n                    props.showAllChildren = true;\n                    el = el.nextElementSibling;\n                }\n                index.push(article);\n            }\n            return index;\n        }\n        for (const el of elements) {\n            const props = {};\n            try {\n                const content = getPropNameNode(\"content\", el);\n                const articles = buildIndex(content.querySelector(\"li\"), props);\n                if (articles.length) {\n                    props.articles = articles;\n                    el.dataset.embeddedProps = JSON.stringify(props);\n                }\n            } catch {\n                // ignore the existing article index if the parsing fails, it will\n                // have to be refreshed manually.\n            }\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.dataset.embedded = \"articleIndex\";\n            el.replaceChildren();\n        }\n    },\n    comments: (elements, env) => {\n        function createBeacon({ type, threadId, resId, resModel, disabled }) {\n            const anchor = document.createElement(\"A\");\n            anchor.classList.add(\"oe_unremovable\", \"oe_thread_beacon\");\n            anchor.dataset.id = threadId;\n            anchor.dataset.res_id = resId;\n            anchor.dataset.resModel = resModel;\n            anchor.dataset.oeType = type;\n            if (disabled) {\n                anchor.classList.add(\"oe_disabled_thread_beacon\");\n            }\n            return anchor;\n        }\n        function createBeacons({ anchors, threadId, resId, resModel }) {\n            const start = anchors.at(0);\n            const end = anchors.at(-1);\n            if (!start || !end) {\n                return;\n            }\n            const disabled = !start.classList.contains(\"knowledge-thread-highlighted-comment\");\n            const beaconStart = createBeacon({\n                type: \"threadBeaconStart\",\n                threadId,\n                resId,\n                resModel,\n                disabled,\n            });\n            const beaconEnd = createBeacon({\n                type: \"threadBeaconEnd\",\n                threadId,\n                resId,\n                resModel,\n                disabled,\n            });\n            start.before(beaconStart);\n            end.after(beaconEnd);\n        }\n        function groupComments(anchors) {\n            const comments = {};\n            for (const anchor of anchors) {\n                const threadId = anchor.dataset.id;\n                if (!threadId) {\n                    continue;\n                }\n                comments[threadId] ||= [];\n                comments[threadId].push(anchor);\n            }\n            return comments;\n        }\n        const resId = env.model?.root?.resId;\n        const resModel = env.model?.root?.resModel;\n        if (resId && resModel) {\n            // Only create new comment beacons if the env has a record.\n            const comments = groupComments(elements);\n            for (const threadId in comments) {\n                createBeacons({ anchors: comments[threadId], threadId, resId, resModel });\n            }\n        }\n        // Remove old comments anchors (not a big deal if there is no replacement for some)\n        for (const el of [...elements]) {\n            if (el.nodeName === \"SPAN\") {\n                const childNodes = [];\n                while (el.firstChild) {\n                    childNodes.push(el.firstChild);\n                    el.firstChild.remove();\n                }\n                el.replaceWith(...childNodes);\n                continue;\n            }\n            el.classList.remove(\n                \"focused-comment\",\n                \"knowledge-thread-highlighted-comment\",\n                \"knowledge-thread-comment\"\n            );\n            delete el.dataset.id;\n            el.removeAttribute(\"tabindex\");\n        }\n    },\n    drawBehavior: (elements) => {\n        for (const el of elements) {\n            const oldProps = decodeDataBehaviorProps(el.dataset.behaviorProps);\n            if (!oldProps || !oldProps.source) {\n                // Abort the conversion if data can not be recovered.\n                // Element will still exist in the DOM as raw data.\n                continue;\n            }\n            const props = {\n                height: oldProps.height,\n                source: oldProps.source,\n                width: oldProps.width,\n            };\n            el.dataset.embedded = \"draw\";\n            el.dataset.embeddedProps = JSON.stringify(props);\n            delete el.dataset.behaviorProps;\n            delete el.dataset.oeTransientContent;\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.replaceChildren();\n        }\n    },\n    embeddedViewBehavior: (elements) => {\n        for (const el of elements) {\n            const oldProps = decodeDataBehaviorProps(el.dataset.behaviorProps);\n            const viewProps = {\n                context: oldProps.context,\n                displayName: oldProps.display_name,\n                favoriteFilters: {},\n                id: oldProps.embedded_view_id,\n                viewType: oldProps.view_type,\n            };\n            if (oldProps.act_window) {\n                viewProps.actWindow = oldProps.act_window;\n            } else {\n                viewProps.actionXmlId = oldProps.action_xml_id;\n            }\n            if (oldProps.additionalViewProps) {\n                viewProps.additionalViewProps = oldProps.additionalViewProps;\n            }\n            if (oldProps.favorites) {\n                // favorites was an array, is now an object\n                for (const filter of oldProps.favorites) {\n                    viewProps.favoriteFilters[filter.name] = filter;\n                }\n            }\n            if (viewProps.context.knowledge_search_model_state) {\n                viewProps.context.knowledge_search_model_state = migrateSearchModelState(\n                    viewProps.context.knowledge_search_model_state\n                );\n            }\n            delete el.dataset.behaviorProps;\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.dataset.embedded = \"view\";\n            el.dataset.embeddedProps = JSON.stringify({ viewProps });\n            el.replaceChildren();\n        }\n    },\n    fileBehavior: (elements) => {\n        for (const el of elements) {\n            const oldProps = decodeDataBehaviorProps(el.dataset.behaviorProps);\n            const htmlFileName = getPropNameNode(\"fileName\", el)?.textContent;\n            const htmlFileExtension = getPropNameNode(\"fileExtension\", el)?.textContent;\n            const htmlFileImageLink = getPropNameNode(\"fileImage\", el)?.querySelector(\"a\");\n            const href = htmlFileImageLink?.getAttribute(\"href\");\n            const mimetype = htmlFileImageLink?.dataset.mimetype;\n            let accessToken, checksum, id, type, url;\n            if (href?.startsWith(getOrigin())) {\n                id = parseInt((href.match(/\\/web\\/(?:content|image)\\/(\\d+)/) || [])[1]);\n                checksum = (href.match(/unique=([^&]+)/) || [])[1];\n                accessToken = (href.match(/access_token=([^&]+)/) || [])[1];\n            }\n            if (!id) {\n                type = \"url\";\n                url = href?.replace(/\\?.*$/, \"\");\n            } else {\n                type = \"binary\";\n            }\n            const fileName = htmlFileName || oldProps?.fileName || _t(\"Untitled\");\n            const extension = htmlFileExtension || oldProps?.fileExtension;\n            let fileData = oldProps?.fileData;\n            // accessToken has been renamed in file_model\n            if (fileData?.accessToken) {\n                fileData.access_token = fileData.accessToken;\n                delete fileData.accessToken;\n            }\n            if (!id && !url && !fileData) {\n                // Abort the conversion if data can not be recovered.\n                // Element will still exist in the DOM as raw data.\n                continue;\n            }\n            if (!fileData) {\n                fileData = {\n                    access_token: accessToken,\n                    checksum,\n                    extension,\n                    filename: fileName,\n                    id,\n                    mimetype,\n                    name: fileName,\n                    type,\n                    url,\n                };\n            }\n            const props = {\n                fileData,\n            };\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            delete el.dataset.behaviorProps;\n            el.dataset.embedded = \"file\";\n            el.dataset.embeddedProps = JSON.stringify(props);\n            el.replaceChildren();\n        }\n    },\n    tableOfContentBehavior: (elements) => {\n        for (const el of elements) {\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.dataset.embedded = \"tableOfContent\";\n            el.replaceChildren();\n        }\n    },\n    templateBehavior: (elements) => {\n        for (const el of elements) {\n            let content = getPropNameNode(\"content\", el);\n            if (!content) {\n                content = document.createElement(\"DIV\");\n                const p = document.createElement(\"P\");\n                const br = document.createElement(\"BR\");\n                p.append(br);\n                content.append(p);\n            }\n            content.removeAttribute(\"class\");\n            delete content.dataset.propName;\n            content.dataset.embeddedEditable = \"clipboardContent\";\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.dataset.embedded = \"clipboard\";\n            el.replaceChildren(content);\n        }\n    },\n    videoBehavior: (elements) => {\n        for (const el of elements) {\n            const oldProps = decodeDataBehaviorProps(el.dataset.behaviorProps);\n            if (!oldProps || !oldProps.platform || !oldProps.videoId) {\n                // Abort the conversion if data can not be recovered.\n                // Element will still exist in the DOM as raw data.\n                continue;\n            }\n            delete el.dataset.behaviorProps;\n            const props = {\n                platform: oldProps.platform,\n                videoId: oldProps.videoId,\n                params: oldProps.params,\n            };\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.dataset.embedded = \"video\";\n            el.dataset.embeddedProps = JSON.stringify(props);\n            el.replaceChildren();\n        }\n    },\n    viewLinkBehavior: (elements) => {\n        for (const el of elements) {\n            const oldProps = decodeDataBehaviorProps(el.dataset.behaviorProps);\n            const viewProps = {\n                context: oldProps.context,\n                displayName: oldProps.name,\n                viewType: oldProps.view_type,\n            };\n            const props = {};\n            if (oldProps.style) {\n                props.linkStyle = oldProps.style;\n            }\n            if (oldProps.act_window) {\n                viewProps.actWindow = oldProps.act_window;\n            } else {\n                viewProps.actionXmlId = oldProps.action_xml_id;\n            }\n            if (viewProps.context.knowledge_search_model_state) {\n                viewProps.context.knowledge_search_model_state = migrateSearchModelState(\n                    viewProps.context.knowledge_search_model_state\n                );\n            }\n            delete el.dataset.behaviorProps;\n            el.removeAttribute(\"class\");\n            el.removeAttribute(\"tabindex\");\n            el.dataset.embedded = \"viewLink\";\n            el.dataset.embeddedProps = JSON.stringify({ viewProps, ...props });\n            el.replaceChildren();\n        }\n    },\n};\n", "const SELECTOR = `[data-embedded=\"view\"],[data-embedded=\"viewLink\"]`;\n\n/**\n * This migration handles the `user_id` field being replaced by `user_ids`.\n *\n * In the new implementation, to have a favorite filter in the\n * `FAVORITE_SHARED_GROUP` of the SearchModel (as opposed to the\n * `FAVORITE_PRIVATE_GROUP`), `user_ids` must either be empty or have more than\n * one value. In Knowledge, all favorites of an embedded view are visible by\n * everyone, so it makes more sense to use `FAVORITE_SHARED_GROUP`, and use\n * an empty list as `user_ids` (as a reminder, embedded view favorites are\n * not real `ir.filters` records, they are stored as html meta data in an\n * article body, so they are not directly related to any user anymore).\n *\n * @param {HTMLElement} container\n */\nexport function migrate(container) {\n    for (const host of container.querySelectorAll(SELECTOR)) {\n        migrateEmbeddedViewIrFilters(host);\n    }\n}\n\nfunction migrateEmbeddedViewIrFilters(host) {\n    if (!host.dataset.embeddedProps) {\n        return;\n    }\n    const embeddedProps = JSON.parse(host.dataset.embeddedProps);\n    if (embeddedProps.viewProps?.context?.knowledge_search_model_state) {\n        migrateSearchModelState(embeddedProps);\n    }\n    if (embeddedProps.viewProps?.favoriteFilters) {\n        migrateFavoriteFilters(embeddedProps);\n    }\n    host.dataset.embeddedProps = JSON.stringify(embeddedProps);\n}\n\nfunction migrateSearchModelState(embeddedProps) {\n    const state = JSON.parse(embeddedProps.viewProps.context.knowledge_search_model_state);\n    if (!state.searchItems) {\n        return;\n    }\n    for (const searchItem of Object.values(state.searchItems)) {\n        if (searchItem.type !== \"favorite\") {\n            continue;\n        }\n        delete searchItem.userId;\n        searchItem.userIds = [];\n    }\n    embeddedProps.viewProps.context.knowledge_search_model_state = JSON.stringify(state);\n}\n\nfunction migrateFavoriteFilters(embeddedProps) {\n    const favoriteFilters = embeddedProps.viewProps.favoriteFilters;\n    for (const favorite of Object.values(favoriteFilters)) {\n        delete favorite.user_id;\n        favorite.user_ids = [];\n    }\n}\n", "/**\n * Convert the string from a data-behavior-props attribute to an usable object.\n *\n * @param {String} dataBehaviorPropsAttribute utf-8 encoded JSON string\n * @returns {Object} object containing props for a Behavior to store in the\n *                   html_field value of a field\n */\nexport function decodeDataBehaviorProps(dataBehaviorPropsAttribute) {\n    if (!dataBehaviorPropsAttribute) {\n        return undefined;\n    }\n    return JSON.parse(decodeURIComponent(dataBehaviorPropsAttribute));\n}\n\n/**\n * Return any existing propName node owned by the Behavior related to `anchor`.\n * Filter out propName nodes owned by children Behavior.\n *\n * @param {string} propName name of the htmlProp\n * @param {Element} anchor node to search for propName children\n * @returns {Element} last matching node (there should be only one, but it's\n *           always the last one that is taken as the effective prop)\n */\nexport function getPropNameNode(propName, anchor) {\n    const propNodes = anchor.querySelectorAll(`[data-prop-name=\"${propName}\"]`);\n    for (let i = propNodes.length - 1; i >= 0; i--) {\n        const closest = propNodes[i].closest(\".o_knowledge_behavior_anchor\");\n        if (closest === anchor) {\n            return propNodes[i];\n        }\n    }\n}\n", "import {\n    EmbeddedComponentInteraction,\n    getEmbeddingMap,\n} from \"@html_editor/public/embedded_components/embedded_component_interaction\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { KNOWLEDGE_PUBLIC_EMBEDDINGS } from \"@website_knowledge/frontend/editor/embedded_components/embedding_sets\";\n\npatch(EmbeddedComponentInteraction.prototype, {\n    setup() {\n        super.setup();\n        this.knowledgePublicViewEl = this.el.closest(\".o_knowledge_public_view\");\n    },\n\n    getEmbedding(name) {\n        let embedding;\n        if (this.knowledgePublicViewEl) {\n            // Restrict Knowledge embedded components in the Knowledge public view.\n            embedding = getEmbeddingMap(KNOWLEDGE_PUBLIC_EMBEDDINGS).get(name);\n        }\n        return embedding ?? super.getEmbedding(name);\n    },\n\n    setupNewComponent({ name, env, props }) {\n        super.setupNewComponent({ name, env, props });\n        if (name === \"view\" || name === \"viewLink\") {\n            const resId = this.el.closest(\".o_knowledge_public_view\")?.dataset.res_id;\n            Object.assign(env, { articleId: resId ? parseInt(resId) : undefined });\n        }\n    },\n});\n", "import { readonlyArticleIndexEmbedding } from \"@knowledge/editor/embedded_components/core/article_index/readonly_article_index\";\nimport { clipboardEmbedding } from \"@knowledge/editor/embedded_components/core/clipboard/embedded_clipboard\";\nimport { readonlyFoldableSectionEmbedding } from \"@knowledge/editor/embedded_components/core/readonly_foldable_section/readonly_foldable_section\";\nimport { viewPlaceholderEmbedding } from \"@website_knowledge/frontend/editor/embedded_components/view/view_placeholder\";\nimport { publicViewLinkEmbedding } from \"@website_knowledge/frontend/editor/embedded_components/view_link/public_embedded_view_link\";\n\nexport const KNOWLEDGE_PUBLIC_EMBEDDINGS = [\n    clipboardEmbedding,\n    publicViewLinkEmbedding,\n    readonlyArticleIndexEmbedding,\n    readonlyFoldableSectionEmbedding,\n    viewPlaceholderEmbedding,\n];\n", "import { Component } from \"@odoo/owl\";\n\nexport class ViewPlaceholderComponent extends Component {\n    static template = \"website_knowledge.ViewPlaceholder\";\n    static props = {};\n\n    setup() {\n        this.url = `/knowledge/article/${this.env.articleId}`;\n    }\n}\n\nexport const viewPlaceholderEmbedding = {\n    name: \"view\",\n    Component: ViewPlaceholderComponent,\n};\n", "import { Component } from \"@odoo/owl\";\nimport { getEmbeddedProps } from \"@html_editor/others/embedded_component_utils\";\nimport { EMBEDDED_VIEW_LINK_STYLES } from \"@knowledge/editor/embedded_components/core/embedded_view_link/embedded_view_link_style\";\n\nexport class PublicEmbeddedViewLinkComponent extends Component {\n    static template = \"knowledge.PublicEmbeddedViewLink\";\n    static props = {\n        viewProps: { type: Object },\n        linkStyle: { type: String, optional: true },\n    };\n    static defaultProps = {\n        linkStyle: \"link\",\n    };\n\n    setup() {\n        this.url = `/knowledge/article/${this.env.articleId}`;\n    }\n\n    getLinkClass() {\n        return EMBEDDED_VIEW_LINK_STYLES[this.props.linkStyle].class;\n    }\n}\n\nexport const publicViewLinkEmbedding = {\n    name: \"viewLink\",\n    Component: PublicEmbeddedViewLinkComponent,\n    getProps: (host) => {\n        return { ...getEmbeddedProps(host) };\n    },\n};\n", "import { HtmlMigrationsInteraction } from \"@html_editor/public/html_migrations/html_migrations_interaction\";\nimport { patch } from \"@web/core/utils/patch\";\n\npatch(HtmlMigrationsInteraction, {\n    selector: [\n        HtmlMigrationsInteraction.selector,\n        `.o_knowledge_public_view:has(.o_knowledge_behavior_anchor)`,\n    ].join(\",\"),\n});\n", "import { markup } from \"@odoo/owl\";\nimport { router } from \"@web/core/browser/router\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { Reactive } from \"@web/core/utils/reactive\";\n\nexport class Article extends Reactive {\n    constructor(id) {\n        super();\n        this.model = \"knowledge.article\";\n        this.resId = id;\n        this.keepLastLoad = new KeepLast();\n    }\n\n    /**\n     * Load the content of a newly opened article which should replace\n     * the current one. The router state and url are updated to properly handle\n     * browser \"next\" and \"previous\".\n     *\n     * @param {Number} id\n     */\n    async load(id) {\n        if (id) {\n            router.pushState({ resId: id }, { replace: true });\n        } else {\n            id = this.resId;\n        }\n        const result = await this.keepLastLoad.add(\n            rpc(`/knowledge/public/article`, { article_id: id })\n        );\n        this.resId = id;\n        this.content = markup(result.content);\n    }\n}\n", "import { reactive, xml } from \"@odoo/owl\";\nimport { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { Article } from \"@website_knowledge/frontend/knowledge_public_view/knowledge_public_article\";\nimport { KnowledgePublicSidebar } from \"@website_knowledge/frontend/sidebar/knowledge_public_sidebar\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { router } from \"@web/core/browser/router\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { stateToUrl, urlToState } from \"@knowledge/portal_webclient/router_utils\";\n\nconst SIDEBAR_ENABLE_BUTTON_SELECTOR = \".o_knowledge_sidebar_enabler_button\";\nconst contentTemplate = xml`<t t-out=\"content\"/>`;\n\nexport class KnowledgePublicViewInteraction extends Interaction {\n    static selector = \".o_knowledge_public_view\";\n    dynamicContent = {\n        _root: {\n            \"t-att-data-res_id\": () => this.subEnv.record.resId,\n        },\n        [SIDEBAR_ENABLE_BUTTON_SELECTOR]: {\n            \"t-on-click\": () => {\n                this.subEnv.toggleSidebar(true);\n            },\n        },\n        \".o_knowledge_sidebar_container\": {\n            \"t-component\": () => {\n                const el = this.el.querySelector(\".o_knowledge_sidebar_container\");\n                return [\n                    KnowledgePublicSidebar,\n                    {\n                        onMounted: () => {\n                            el.classList.remove(\"o_knowledge_sidebar_loading\");\n                        },\n                        subEnv: this.subEnv,\n                        viewState: this.state,\n                    },\n                ];\n            },\n        },\n        \".o_knowledge_article_log_in\": {\n            \"t-att-href\": () =>\n                `/web/login?redirect=/knowledge/article/${this.subEnv.record.resId}`,\n        },\n    };\n\n    setup() {\n        this.interactionService = this.services[\"public.interactions\"];\n        this.state = reactive({\n            showSidebar: false,\n        });\n        const handleButtonVisibility = () => {\n            const button = this.el.querySelector(SIDEBAR_ENABLE_BUTTON_SELECTOR);\n            button?.classList[this.state.showSidebar ? \"add\" : \"remove\"](\"d-none\");\n        };\n        this.subEnv = {\n            record: new Article(parseInt(this.el.dataset.res_id)),\n            openArticle: async (articleId) => {\n                await this.waitFor(this.subEnv.record.load(articleId));\n                const container = this.el.querySelector(\".o_knowledge_public_content_container\");\n                this.interactionService.stopInteractions(container);\n                container.replaceChildren();\n                // Interactions are restarted during the renderAt process.\n                this.renderAt(contentTemplate, { content: this.subEnv.record.content }, container);\n                if (this.env.isSmall) {\n                    this.subEnv.toggleSidebar(false);\n                } else {\n                    handleButtonVisibility();\n                }\n            },\n            toggleSidebar: (showSidebar) => {\n                showSidebar ??= !this.state.showSidebar;\n                this.state.showSidebar = showSidebar;\n                handleButtonVisibility();\n            },\n        };\n        this.unpatchRouter = patch(router, {\n            stateToUrl,\n            urlToState,\n        });\n        router.replaceState(router.urlToState(new URL(browser.location)));\n    }\n\n    async willStart() {\n        if (this.el.querySelector(\".o_knowledge_sidebar_container\")) {\n            this.subEnv.toggleSidebar(!this.env.isSmall);\n        }\n    }\n\n    destroy() {\n        this.unpatchRouter();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_knowledge.article_public_view\", KnowledgePublicViewInteraction);\n", "import { rpc } from \"@web/core/network/rpc\";\nimport { ResizablePanel } from \"@web/core/resizable_panel/resizable_panel\";\nimport { Transition } from \"@web/core/transition\";\nimport { useBus, useChildRef, useForwardRefToParent, useService } from \"@web/core/utils/hooks\";\nimport { ArticleSearchDialog } from \"@knowledge/components/article_search_dialog/article_search_dialog\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { router, routerBus } from \"@web/core/browser/router\";\n\nimport {\n    Component,\n    onMounted,\n    onWillStart,\n    useEffect,\n    useExternalListener,\n    useState,\n    useSubEnv,\n} from \"@odoo/owl\";\n\nclass KnowledgePublicResizablePanel extends ResizablePanel {\n    static props = {\n        ...ResizablePanel.props,\n        handleRef: { type: Function },\n    };\n\n    setup() {\n        super.setup();\n        useForwardRefToParent(\"handleRef\");\n    }\n}\n\nclass SidebarArticle {\n    /**\n     * @param {integer} id\n     * @param {string} icon\n     * @param {string} name\n     */\n    constructor(id, icon, name) {\n        this.id = id;\n        this.icon = icon;\n        this.name = name;\n        this.children = [];\n        this.isUnfolded = false;\n        this.childrenAreLoaded = false;\n        this.parent = null;\n    }\n\n    async loadChildren() {\n        const children = await rpc(\"/knowledge/public/children\", { article_id: this.id });\n        this.children = children.map((child) => new SidebarArticle(child.id, child.icon, child.name));\n        this.childrenAreLoaded = true;\n    }\n\n    async toggleFold() {\n        if (!this.isUnfolded && !this.childrenAreLoaded) {\n            await this.loadChildren();\n        }\n        this.isUnfolded = !this.isUnfolded;\n    }\n}\n\nexport class KnowledgePublicSidebar extends Component {\n    static template = \"website_knowledge.publicSidebar\";\n    static components = { ResizablePanel: KnowledgePublicResizablePanel, Transition };\n    static props = {\n        onMounted: Function,\n        subEnv: Object,\n        viewState: Object,\n    };\n\n    setup() {\n        useSubEnv(this.props.subEnv);\n        this.rootArticle = useState(new SidebarArticle(null, null, null));\n        this.articlesMap = new Map();\n        this.record = useState(this.env.record);\n        this.dialog = useService(\"dialog\");\n        this.keepLastSidebarLoad = new KeepLast();\n        onWillStart(() => {\n            this.loadSidebar();\n        });\n        onMounted(() => {\n            this.props.onMounted();\n        });\n        useEffect(\n            () => {\n                const article = this.articlesMap.get(this.record.resId);\n                if (this.props.viewState.showSidebar && !article?.isUnfolded) {\n                    this.loadSidebar();\n                }\n            },\n            () => [this.record.resId, this.props.viewState.showSidebar]\n        );\n        useBus(routerBus, \"ROUTE_CHANGE\", () => this.env.openArticle(router.current.resId));\n        // Handle color while dragging\n        this.handleRef = useChildRef();\n        const mouseDownHandler = () => {\n            this.handleRef.el.classList.add(\"o_knowledge_sidebar_active_handle\");\n        };\n        useEffect(\n            (el) => {\n                if (el) {\n                    el.addEventListener(\"mousedown\", mouseDownHandler);\n                    return () => el.removeEventListener(\"mousedown\", mouseDownHandler);\n                }\n            },\n            () => [this.handleRef.el]\n        );\n        useExternalListener(document, \"mouseup\", () => {\n            this.handleRef.el?.classList.remove(\"o_knowledge_sidebar_active_handle\");\n        });\n    }\n\n    async loadSidebar() {\n        const articles = await this.keepLastSidebarLoad.add(\n            rpc(\"/knowledge/public/sidebar\", {\n                article_id: this.env.record.resId,\n            })\n        );\n        this.articlesMap = new Map();\n        articles.forEach((article) => {\n            this.articlesMap.set(article.id, new SidebarArticle(article.id, article.icon, article.name));\n        });\n        articles.forEach((article) => {\n            const currentArticle = this.articlesMap.get(article.id);\n            const parent = this.articlesMap.get(article.parent_id);\n            if (parent) {\n                parent.children.push(currentArticle);\n                currentArticle.parent = parent;\n                if (!parent.isUnfolded) {\n                    parent.isUnfolded = true;\n                }\n            } else {\n                // no parent or parent not published -> article is subsite root\n                Object.assign(this.rootArticle, currentArticle);\n            }\n        });\n        // case where root was processed before its children articles\n        if (this.rootArticle.children.length) {\n            this.rootArticle.isUnfolded = true;\n        }\n    }\n\n    openSearchDialog() {\n        this.dialog.add(ArticleSearchDialog, {\n            search: (searchValue) =>\n                rpc(`/knowledge/public/search`, {\n                    search_value: searchValue,\n                    subsite_root_id: this.rootArticle.id,\n                }),\n            select: (article) => this.env.openArticle(article.id).then(() => this.loadSidebar()),\n        });\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { getDataURLFromFile } from \"@web/core/utils/urls\";\nimport { checkFileSize } from \"@web/core/utils/files\";\n\nimport { Component, useRef, useState } from \"@odoo/owl\";\n\nexport class FileUploader extends Component {\n    static template = \"web.FileUploader\";\n    static props = {\n        onClick: { type: Function, optional: true },\n        onUploaded: Function,\n        onUploadComplete: { type: Function, optional: true },\n        multiUpload: { type: Boolean, optional: true },\n        checkSize: { type: Boolean, optional: true },\n        inputName: { type: String, optional: true },\n        fileUploadClass: { type: String, optional: true },\n        acceptedFileExtensions: { type: String, optional: true },\n        slots: { type: Object, optional: true },\n        showUploadingText: { type: Boolean, optional: true },\n        // See https://www.iana.org/assignments/media-types/media-types.xhtml\n        allowedMIMETypes: { type: String, optional: true },\n    };\n    static defaultProps = {\n        checkSize: true,\n        showUploadingText: true,\n    };\n\n    setup() {\n        this.notification = useService(\"notification\");\n        this.fileInputRef = useRef(\"fileInput\");\n        this.state = useState({\n            isUploading: false,\n        });\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    async onFileChange(ev) {\n        const files = [...ev.target.files].filter(file => this.validFileType(file));\n        if (!files. length) {\n            return;\n        }\n        const { target } = ev;\n        for (const file of files) {\n            if (this.props.checkSize && !checkFileSize(file.size, this.notification)) {\n                return null;\n            }\n            this.state.isUploading = true;\n            const data = await getDataURLFromFile(file);\n            if (!file.size) {\n                console.warn(`Error while uploading file : ${file.name}`);\n                this.notification.add(_t(\"There was a problem while uploading your file.\"), {\n                    type: \"danger\",\n                });\n            }\n            try {\n                await this.props.onUploaded({\n                    name: file.name,\n                    size: file.size,\n                    type: file.type,\n                    data: data.split(\",\")[1],\n                    objectUrl: file.type === \"application/pdf\" ? URL.createObjectURL(file) : null,\n                });\n            } finally {\n                this.state.isUploading = false;\n            }\n        }\n        target.value = null;\n        if (this.props.multiUpload && this.props.onUploadComplete) {\n            this.props.onUploadComplete({});\n        }\n    }\n\n    /**\n     * The `allowedMIMETypes` props can restrict the file types users are guided to select.\n     * However, the `acceptedFileExtensions` attribute doesn't enforce strict validation;\n     * it only suggests file types for browsers.\n     *\n     * @param {File} file\n     * @returns Whether the upload file's type is in the whitelist (`allowedMIMETypes`).\n     */\n     validFileType(file) {\n        if (this.props.allowedMIMETypes && !this.props.allowedMIMETypes.includes(file.type)) {\n            this.notification.add(\n                _t(`Oops! '%(fileName)s' didn\u2019t upload since its format isn\u2019t allowed.`, {\n                    fileName: file.name,\n                }),\n                {\n                    type: \"danger\",\n                }\n            );\n            return false;\n        }\n        return true;\n    }\n\n    async onSelectFileButtonClick(ev) {\n        if (this.props.onClick) {\n            const ok = await this.props.onClick(ev);\n            if (ok !== undefined && !ok) {\n                return;\n            }\n        }\n        this.fileInputRef.el.click();\n    }\n}\n", "import { Wysiwyg } from \"@html_editor/wysiwyg\";\nimport { Component, markup, onMounted, onWillStart, reactive, useRef, useState } from \"@odoo/owl\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { localization } from \"@web/core/l10n/localization\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { user } from \"@web/core/user\";\nimport { useAutofocus, useService } from \"@web/core/utils/hooks\";\nimport { isHtmlEmpty } from \"@web/core/utils/html\";\nimport { isEmail } from \"@web/core/utils/strings\";\nimport { FileUploader } from \"@web/views/fields/file_handler\";\nimport { endPos } from \"@html_editor/utils/position\";\n\nexport class ProfileDialog extends Component {\n    static template = \"website_profile.ProfileDialog\";\n    static components = {\n        Dialog,\n        FileUploader,\n        Wysiwyg,\n    };\n    static props = {\n        close: Function,\n        confirm: { type: Function, optional: true },\n        focusWebsiteDescription: {\n            type: Boolean,\n            optional: true,\n        },\n        userId: { type: Number },\n    };\n    static defaultProps = {\n        confirm: () => {},\n        focusWebsiteDescription: false,\n    };\n\n    setup() {\n        super.setup();\n        this.orm = useService(\"orm\");\n        this.upload = useRef(\"upload\");\n        this.profileImg = useRef(\"profileImg\");\n        this.profileImgData = null;\n        this.state = useState({\n            isProcessing: false,\n            hasError: false,\n            emailHasError: false,\n            nameHasError: false,\n        });\n        const websiteDescriptionClass = \"website_profile_profile_dialog_website_description\";\n        useAutofocus({ refName: \"name\" });\n\n        onWillStart(async () => {\n            const [users, countries] = await Promise.all([\n                this.orm.read(\n                    \"res.users\",\n                    [this.props.userId],\n                    [\n                        \"city\",\n                        \"country_id\",\n                        \"email\",\n                        \"name\",\n                        \"website\",\n                        \"website_description\",\n                        \"website_published\",\n                    ]\n                ),\n                this.orm.searchRead(\"res.country\", [], [\"id\", \"name\"]),\n            ]);\n            const userData = users[0];\n            userData.country_id = userData.country_id && userData.country_id[0]; // keep only id\n            userData.website_description = markup(userData.website_description || \"\");\n            this.user = reactive(userData, () => this.validate());\n            this.countries = countries;\n            const isInternalUser = await user.hasGroup(\"base.group_user\");\n            this.descriptionWysiwygConfig = {\n                allowFile: isInternalUser,\n                allowImage: isInternalUser,\n                classList: [\"form-control\", websiteDescriptionClass],\n                content: this.user.website_description,\n                debug: !!this.env.debug,\n                direction: localization.direction || \"ltr\",\n                placeholder: _t(\"Write a few words about yourself...\"),\n            };\n        });\n\n        onMounted(() => {\n            if (this.props.focusWebsiteDescription) {\n                const websiteDescription = document.querySelector(\n                    `.${websiteDescriptionClass}[contenteditable=\"true\"]`\n                );\n                if (websiteDescription) {\n                    document.getSelection()?.setPosition(...endPos(websiteDescription));\n                }\n            }\n            this.validate();\n        });\n    }\n\n    validate() {\n        this.state.emailHasError = !isEmail(this.user.email);\n        this.state.nameHasError = !this.user.name || !this.user.name.trim().length;\n        this.state.hasError = this.state.emailHasError || this.state.nameHasError;\n    }\n\n    get isEditingMyProfile() {\n        return user.userId === this.props.userId;\n    }\n\n    get title() {\n        return _t(\"Edit Profile\");\n    }\n\n    onClearProfileImg() {\n        this.profileImgData = false;\n        this.profileImg.el.src = \"/web/static/img/placeholder.png\";\n    }\n\n    async onConfirm() {\n        this.state.isProcessing = true;\n        const descriptionElContent = this.websiteDescriptionEditor.getElContent();\n        const data = {\n            ...this.user,\n            country_id: this.user.country_id && parseInt(this.user.country_id),\n            website_description: isHtmlEmpty(descriptionElContent.innerText)\n                ? \"\"\n                : descriptionElContent.innerHTML,\n            user_id: this.props.userId,\n        };\n        if (this.profileImgData != null) {\n            data.image_1920 = this.profileImgData;\n        }\n        await rpc(\"/profile/user/save\", data);\n        if (this.props.confirm) {\n            await this.props.confirm();\n        }\n        this.props.close();\n    }\n\n    onUploadProfileImg(file) {\n        this.profileImg.el.src = `data:${file.type};base64,${file.data}`;\n        this.profileImgData = file.data;\n    }\n\n    onWebsiteDescriptionEditorLoad(editor) {\n        this.websiteDescriptionEditor = editor;\n    }\n}\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class NextRankCard extends Interaction {\n    static selector = \".o_wprofile_progress_circle\";\n\n    setup() {\n        const tooltip = window.Tooltip.getOrCreateInstance(this.el.querySelector('g[data-bs-toggle=\"tooltip\"]'));\n        this.registerCleanup(() => tooltip.dispose());\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_profile.next_rank_card\", NextRankCard);\n", "import { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { ProfileDialog } from \"../components/profile_dialog/profile_dialog\";\n\nexport class ProfileEditor extends Interaction {\n    static selector = \".o_wprofile_editor\";\n    dynamicContent = {\n        _root: {\n            \"t-on-click.prevent\": this.openDialog,\n        },\n    };\n\n    openDialog() {\n        this.services.dialog.add(ProfileDialog, {\n            confirm: () => {\n                browser.location.reload();\n            },\n            focusWebsiteDescription:\n                this.el.dataset.focusWebsiteDescription &&\n                this.el.dataset.focusWebsiteDescription === \"true\",\n            userId: parseInt(this.el.dataset.userId),\n        });\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_profile.profile_editor\", ProfileEditor);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { redirect } from \"@web/core/utils/urls\";\nimport { rpc } from \"@web/core/network/rpc\";\n\nexport class ProfileValidation extends Interaction {\n    static selector = \".o_wprofile_email_validation_container\";\n    dynamicContent = {\n        \".send_validation_email\": {\n            \"t-on-click.prevent.withTarget\": this.locked(this.onSendMailClick, true),\n        },\n        \".validated_email_close\": { \"t-on-click\": () => rpc(\"/profile/validate_email/close\") },\n    };\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onSendMailClick(ev, currentTargetEl) {\n        const data = await this.waitFor(rpc('/profile/send_validation_email', {\n            redirect_url: currentTargetEl.dataset[\"redirect_url\"],\n        }));\n        if (data) {\n            redirect(currentTargetEl.dataset[\"redirect_url\"]);\n            return new Promise(() => {});\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_profile.profile_validation\", ProfileValidation);\n", "/*! @license DOMPurify 3.1.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMPurify = factory());\n})(this, (function () { 'use strict';\n\n  const {\n    entries,\n    setPrototypeOf,\n    isFrozen,\n    getPrototypeOf,\n    getOwnPropertyDescriptor\n  } = Object;\n  let {\n    freeze,\n    seal,\n    create\n  } = Object; // eslint-disable-line import/no-mutable-exports\n  let {\n    apply,\n    construct\n  } = typeof Reflect !== 'undefined' && Reflect;\n  if (!freeze) {\n    freeze = function freeze(x) {\n      return x;\n    };\n  }\n  if (!seal) {\n    seal = function seal(x) {\n      return x;\n    };\n  }\n  if (!apply) {\n    apply = function apply(fun, thisValue, args) {\n      return fun.apply(thisValue, args);\n    };\n  }\n  if (!construct) {\n    construct = function construct(Func, args) {\n      return new Func(...args);\n    };\n  }\n  const arrayForEach = unapply(Array.prototype.forEach);\n  const arrayPop = unapply(Array.prototype.pop);\n  const arrayPush = unapply(Array.prototype.push);\n  const stringToLowerCase = unapply(String.prototype.toLowerCase);\n  const stringToString = unapply(String.prototype.toString);\n  const stringMatch = unapply(String.prototype.match);\n  const stringReplace = unapply(String.prototype.replace);\n  const stringIndexOf = unapply(String.prototype.indexOf);\n  const stringTrim = unapply(String.prototype.trim);\n  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\n  const regExpTest = unapply(RegExp.prototype.test);\n  const typeErrorCreate = unconstruct(TypeError);\n\n  /**\n   * Creates a new function that calls the given function with a specified thisArg and arguments.\n   *\n   * @param {Function} func - The function to be wrapped and called.\n   * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.\n   */\n  function unapply(func) {\n    return function (thisArg) {\n      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n        args[_key - 1] = arguments[_key];\n      }\n      return apply(func, thisArg, args);\n    };\n  }\n\n  /**\n   * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n   *\n   * @param {Function} func - The constructor function to be wrapped and called.\n   * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.\n   */\n  function unconstruct(func) {\n    return function () {\n      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n        args[_key2] = arguments[_key2];\n      }\n      return construct(func, args);\n    };\n  }\n\n  /**\n   * Add properties to a lookup table\n   *\n   * @param {Object} set - The set to which elements will be added.\n   * @param {Array} array - The array containing elements to be added to the set.\n   * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n   * @returns {Object} The modified set with added elements.\n   */\n  function addToSet(set, array) {\n    let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;\n    if (setPrototypeOf) {\n      // Make 'in' and truthy checks like Boolean(set.constructor)\n      // independent of any properties defined on Object.prototype.\n      // Prevent prototype setters from intercepting set as a this value.\n      setPrototypeOf(set, null);\n    }\n    let l = array.length;\n    while (l--) {\n      let element = array[l];\n      if (typeof element === 'string') {\n        const lcElement = transformCaseFunc(element);\n        if (lcElement !== element) {\n          // Config presets (e.g. tags.js, attrs.js) are immutable.\n          if (!isFrozen(array)) {\n            array[l] = lcElement;\n          }\n          element = lcElement;\n        }\n      }\n      set[element] = true;\n    }\n    return set;\n  }\n\n  /**\n   * Clean up an array to harden against CSPP\n   *\n   * @param {Array} array - The array to be cleaned.\n   * @returns {Array} The cleaned version of the array\n   */\n  function cleanArray(array) {\n    for (let index = 0; index < array.length; index++) {\n      const isPropertyExist = objectHasOwnProperty(array, index);\n      if (!isPropertyExist) {\n        array[index] = null;\n      }\n    }\n    return array;\n  }\n\n  /**\n   * Shallow clone an object\n   *\n   * @param {Object} object - The object to be cloned.\n   * @returns {Object} A new object that copies the original.\n   */\n  function clone(object) {\n    const newObject = create(null);\n    for (const [property, value] of entries(object)) {\n      const isPropertyExist = objectHasOwnProperty(object, property);\n      if (isPropertyExist) {\n        if (Array.isArray(value)) {\n          newObject[property] = cleanArray(value);\n        } else if (value && typeof value === 'object' && value.constructor === Object) {\n          newObject[property] = clone(value);\n        } else {\n          newObject[property] = value;\n        }\n      }\n    }\n    return newObject;\n  }\n\n  /**\n   * This method automatically checks if the prop is function or getter and behaves accordingly.\n   *\n   * @param {Object} object - The object to look up the getter function in its prototype chain.\n   * @param {String} prop - The property name for which to find the getter function.\n   * @returns {Function} The getter function found in the prototype chain or a fallback function.\n   */\n  function lookupGetter(object, prop) {\n    while (object !== null) {\n      const desc = getOwnPropertyDescriptor(object, prop);\n      if (desc) {\n        if (desc.get) {\n          return unapply(desc.get);\n        }\n        if (typeof desc.value === 'function') {\n          return unapply(desc.value);\n        }\n      }\n      object = getPrototypeOf(object);\n    }\n    function fallbackValue() {\n      return null;\n    }\n    return fallbackValue;\n  }\n\n  const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);\n\n  // SVG\n  const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\n  const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);\n\n  // List of SVG elements that are disallowed by default.\n  // We still need to know them so that we can do namespace\n  // checks properly in case one wants to add them to\n  // allow-list.\n  const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);\n  const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);\n\n  // Similarly to SVG, we want to know all MathML elements,\n  // even those that we disallow by default.\n  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\n  const text = freeze(['#text']);\n\n  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);\n  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);\n  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);\n  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n  // eslint-disable-next-line unicorn/better-regex\n  const MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\n  const ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\n  const TMPLIT_EXPR = seal(/\\${[\\w\\W]*}/gm);\n  const DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]/); // eslint-disable-line no-useless-escape\n  const ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\n  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n  );\n  const IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\n  const ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n  );\n  const DOCTYPE_NAME = seal(/^html$/i);\n  const CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\n  var EXPRESSIONS = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    MUSTACHE_EXPR: MUSTACHE_EXPR,\n    ERB_EXPR: ERB_EXPR,\n    TMPLIT_EXPR: TMPLIT_EXPR,\n    DATA_ATTR: DATA_ATTR,\n    ARIA_ATTR: ARIA_ATTR,\n    IS_ALLOWED_URI: IS_ALLOWED_URI,\n    IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,\n    ATTR_WHITESPACE: ATTR_WHITESPACE,\n    DOCTYPE_NAME: DOCTYPE_NAME,\n    CUSTOM_ELEMENT: CUSTOM_ELEMENT\n  });\n\n  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\n  const NODE_TYPE = {\n    element: 1,\n    attribute: 2,\n    text: 3,\n    cdataSection: 4,\n    entityReference: 5,\n    // Deprecated\n    entityNode: 6,\n    // Deprecated\n    progressingInstruction: 7,\n    comment: 8,\n    document: 9,\n    documentType: 10,\n    documentFragment: 11,\n    notation: 12 // Deprecated\n  };\n  const getGlobal = function getGlobal() {\n    return typeof window === 'undefined' ? null : window;\n  };\n\n  /**\n   * Creates a no-op policy for internal use only.\n   * Don't export this function outside this module!\n   * @param {TrustedTypePolicyFactory} trustedTypes The policy factory.\n   * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n   * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types\n   * are not supported or creating the policy failed).\n   */\n  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {\n    if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {\n      return null;\n    }\n\n    // Allow the callers to control the unique policy name\n    // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n    // Policy creation with duplicate names throws in Trusted Types.\n    let suffix = null;\n    const ATTR_NAME = 'data-tt-policy-suffix';\n    if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n      suffix = purifyHostElement.getAttribute(ATTR_NAME);\n    }\n    const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n    try {\n      return trustedTypes.createPolicy(policyName, {\n        createHTML(html) {\n          return html;\n        },\n        createScriptURL(scriptUrl) {\n          return scriptUrl;\n        }\n      });\n    } catch (_) {\n      // Policy creation failed (most likely another DOMPurify script has\n      // already run). Skip creating the policy, as this will only cause errors\n      // if TT are enforced.\n      console.warn('TrustedTypes policy ' + policyName + ' could not be created.');\n      return null;\n    }\n  };\n  function createDOMPurify() {\n    let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n    const DOMPurify = root => createDOMPurify(root);\n\n    /**\n     * Version label, exposed for easier checks\n     * if DOMPurify is up to date or not\n     */\n    DOMPurify.version = '3.1.7';\n\n    /**\n     * Array of elements that DOMPurify removed during sanitation.\n     * Empty if nothing was removed.\n     */\n    DOMPurify.removed = [];\n    if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {\n      // Not running in a browser, provide a factory function\n      // so that you can pass your own Window\n      DOMPurify.isSupported = false;\n      return DOMPurify;\n    }\n    let {\n      document\n    } = window;\n    const originalDocument = document;\n    const currentScript = originalDocument.currentScript;\n    const {\n      DocumentFragment,\n      HTMLTemplateElement,\n      Node,\n      Element,\n      NodeFilter,\n      NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,\n      HTMLFormElement,\n      DOMParser,\n      trustedTypes\n    } = window;\n    const ElementPrototype = Element.prototype;\n    const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n    const remove = lookupGetter(ElementPrototype, 'remove');\n    const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n    const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n    const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n\n    // As per issue #47, the web-components registry is inherited by a\n    // new document created via createHTMLDocument. As per the spec\n    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n    // a new empty registry is used when creating a template contents owner\n    // document, so we use that as our parent document to ensure nothing\n    // is inherited.\n    if (typeof HTMLTemplateElement === 'function') {\n      const template = document.createElement('template');\n      if (template.content && template.content.ownerDocument) {\n        document = template.content.ownerDocument;\n      }\n    }\n    let trustedTypesPolicy;\n    let emptyHTML = '';\n    const {\n      implementation,\n      createNodeIterator,\n      createDocumentFragment,\n      getElementsByTagName\n    } = document;\n    const {\n      importNode\n    } = originalDocument;\n    let hooks = {};\n\n    /**\n     * Expose whether this browser supports running the full DOMPurify.\n     */\n    DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;\n    const {\n      MUSTACHE_EXPR,\n      ERB_EXPR,\n      TMPLIT_EXPR,\n      DATA_ATTR,\n      ARIA_ATTR,\n      IS_SCRIPT_OR_DATA,\n      ATTR_WHITESPACE,\n      CUSTOM_ELEMENT\n    } = EXPRESSIONS;\n    let {\n      IS_ALLOWED_URI: IS_ALLOWED_URI$1\n    } = EXPRESSIONS;\n\n    /**\n     * We consider the elements and attributes below to be safe. Ideally\n     * don't add any new ones but feel free to remove unwanted ones.\n     */\n\n    /* allowed element names */\n    let ALLOWED_TAGS = null;\n    const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);\n\n    /* Allowed attribute names */\n    let ALLOWED_ATTR = null;\n    const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);\n\n    /*\n     * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.\n     * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n     * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n     * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n     */\n    let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {\n      tagNameCheck: {\n        writable: true,\n        configurable: false,\n        enumerable: true,\n        value: null\n      },\n      attributeNameCheck: {\n        writable: true,\n        configurable: false,\n        enumerable: true,\n        value: null\n      },\n      allowCustomizedBuiltInElements: {\n        writable: true,\n        configurable: false,\n        enumerable: true,\n        value: false\n      }\n    }));\n\n    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n    let FORBID_TAGS = null;\n\n    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n    let FORBID_ATTR = null;\n\n    /* Decide if ARIA attributes are okay */\n    let ALLOW_ARIA_ATTR = true;\n\n    /* Decide if custom data attributes are okay */\n    let ALLOW_DATA_ATTR = true;\n\n    /* Decide if unknown protocols are okay */\n    let ALLOW_UNKNOWN_PROTOCOLS = false;\n\n    /* Decide if self-closing tags in attributes are allowed.\n     * Usually removed due to a mXSS issue in jQuery 3.0 */\n    let ALLOW_SELF_CLOSE_IN_ATTR = true;\n\n    /* Output should be safe for common template engines.\n     * This means, DOMPurify removes data attributes, mustaches and ERB\n     */\n    let SAFE_FOR_TEMPLATES = false;\n\n    /* Output should be safe even for XML used within HTML and alike.\n     * This means, DOMPurify removes comments when containing risky content.\n     */\n    let SAFE_FOR_XML = true;\n\n    /* Decide if document with <html>... should be returned */\n    let WHOLE_DOCUMENT = false;\n\n    /* Track whether config is already set on this instance of DOMPurify. */\n    let SET_CONFIG = false;\n\n    /* Decide if all elements (e.g. style, script) must be children of\n     * document.body. By default, browsers might move them to document.head */\n    let FORCE_BODY = false;\n\n    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n     * string (or a TrustedHTML object if Trusted Types are supported).\n     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n     */\n    let RETURN_DOM = false;\n\n    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n     * string  (or a TrustedHTML object if Trusted Types are supported) */\n    let RETURN_DOM_FRAGMENT = false;\n\n    /* Try to return a Trusted Type object instead of a string, return a string in\n     * case Trusted Types are not supported  */\n    let RETURN_TRUSTED_TYPE = false;\n\n    /* Output should be free from DOM clobbering attacks?\n     * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n     */\n    let SANITIZE_DOM = true;\n\n    /* Achieve full DOM Clobbering protection by isolating the namespace of named\n     * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n     *\n     * HTML/DOM spec rules that enable DOM Clobbering:\n     *   - Named Access on Window (\u00a77.3.3)\n     *   - DOM Tree Accessors (\u00a73.1.5)\n     *   - Form Element Parent-Child Relations (\u00a74.10.3)\n     *   - Iframe srcdoc / Nested WindowProxies (\u00a74.8.5)\n     *   - HTMLCollection (\u00a74.2.10.2)\n     *\n     * Namespace isolation is implemented by prefixing `id` and `name` attributes\n     * with a constant string, i.e., `user-content-`\n     */\n    let SANITIZE_NAMED_PROPS = false;\n    const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n\n    /* Keep element content when removing element? */\n    let KEEP_CONTENT = true;\n\n    /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n     * of importing it into a new Document and returning a sanitized copy */\n    let IN_PLACE = false;\n\n    /* Allow usage of profiles like html, svg and mathMl */\n    let USE_PROFILES = {};\n\n    /* Tags to ignore content of when KEEP_CONTENT is true */\n    let FORBID_CONTENTS = null;\n    const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);\n\n    /* Tags that are safe for data: URIs */\n    let DATA_URI_TAGS = null;\n    const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);\n\n    /* Attributes safe for values like \"javascript:\" */\n    let URI_SAFE_ATTRIBUTES = null;\n    const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);\n    const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n    const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n    const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n    /* Document namespace */\n    let NAMESPACE = HTML_NAMESPACE;\n    let IS_EMPTY_INPUT = false;\n\n    /* Allowed XHTML+XML namespaces */\n    let ALLOWED_NAMESPACES = null;\n    const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);\n\n    /* Parsing of strict XHTML documents */\n    let PARSER_MEDIA_TYPE = null;\n    const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];\n    const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';\n    let transformCaseFunc = null;\n\n    /* Keep a reference to config to pass to hooks */\n    let CONFIG = null;\n\n    /* Ideally, do not touch anything below this line */\n    /* ______________________________________________ */\n\n    const formElement = document.createElement('form');\n    const isRegexOrFunction = function isRegexOrFunction(testValue) {\n      return testValue instanceof RegExp || testValue instanceof Function;\n    };\n\n    /**\n     * _parseConfig\n     *\n     * @param  {Object} cfg optional config literal\n     */\n    // eslint-disable-next-line complexity\n    const _parseConfig = function _parseConfig() {\n      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      if (CONFIG && CONFIG === cfg) {\n        return;\n      }\n\n      /* Shield configuration object from tampering */\n      if (!cfg || typeof cfg !== 'object') {\n        cfg = {};\n      }\n\n      /* Shield configuration object from prototype pollution */\n      cfg = clone(cfg);\n      PARSER_MEDIA_TYPE =\n      // eslint-disable-next-line unicorn/prefer-includes\n      SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;\n\n      // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.\n      transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;\n\n      /* Set configuration parameters */\n      ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;\n      ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;\n      ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;\n      URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),\n      // eslint-disable-line indent\n      cfg.ADD_URI_SAFE_ATTR,\n      // eslint-disable-line indent\n      transformCaseFunc // eslint-disable-line indent\n      ) // eslint-disable-line indent\n      : DEFAULT_URI_SAFE_ATTRIBUTES;\n      DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS),\n      // eslint-disable-line indent\n      cfg.ADD_DATA_URI_TAGS,\n      // eslint-disable-line indent\n      transformCaseFunc // eslint-disable-line indent\n      ) // eslint-disable-line indent\n      : DEFAULT_DATA_URI_TAGS;\n      FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;\n      FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};\n      FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};\n      USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;\n      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true\n      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true\n      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false\n      ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true\n      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false\n      SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true\n      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false\n      RETURN_DOM = cfg.RETURN_DOM || false; // Default false\n      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false\n      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false\n      FORCE_BODY = cfg.FORCE_BODY || false; // Default false\n      SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true\n      SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false\n      KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true\n      IN_PLACE = cfg.IN_PLACE || false; // Default false\n      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;\n      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;\n      CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;\n      }\n      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {\n        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;\n      }\n      if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {\n        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;\n      }\n      if (SAFE_FOR_TEMPLATES) {\n        ALLOW_DATA_ATTR = false;\n      }\n      if (RETURN_DOM_FRAGMENT) {\n        RETURN_DOM = true;\n      }\n\n      /* Parse profile info */\n      if (USE_PROFILES) {\n        ALLOWED_TAGS = addToSet({}, text);\n        ALLOWED_ATTR = [];\n        if (USE_PROFILES.html === true) {\n          addToSet(ALLOWED_TAGS, html$1);\n          addToSet(ALLOWED_ATTR, html);\n        }\n        if (USE_PROFILES.svg === true) {\n          addToSet(ALLOWED_TAGS, svg$1);\n          addToSet(ALLOWED_ATTR, svg);\n          addToSet(ALLOWED_ATTR, xml);\n        }\n        if (USE_PROFILES.svgFilters === true) {\n          addToSet(ALLOWED_TAGS, svgFilters);\n          addToSet(ALLOWED_ATTR, svg);\n          addToSet(ALLOWED_ATTR, xml);\n        }\n        if (USE_PROFILES.mathMl === true) {\n          addToSet(ALLOWED_TAGS, mathMl$1);\n          addToSet(ALLOWED_ATTR, mathMl);\n          addToSet(ALLOWED_ATTR, xml);\n        }\n      }\n\n      /* Merge configuration parameters */\n      if (cfg.ADD_TAGS) {\n        if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n          ALLOWED_TAGS = clone(ALLOWED_TAGS);\n        }\n        addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n      }\n      if (cfg.ADD_ATTR) {\n        if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n          ALLOWED_ATTR = clone(ALLOWED_ATTR);\n        }\n        addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n      }\n      if (cfg.ADD_URI_SAFE_ATTR) {\n        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);\n      }\n      if (cfg.FORBID_CONTENTS) {\n        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n          FORBID_CONTENTS = clone(FORBID_CONTENTS);\n        }\n        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);\n      }\n\n      /* Add #text in case KEEP_CONTENT is set to true */\n      if (KEEP_CONTENT) {\n        ALLOWED_TAGS['#text'] = true;\n      }\n\n      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n      if (WHOLE_DOCUMENT) {\n        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n      }\n\n      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n      if (ALLOWED_TAGS.table) {\n        addToSet(ALLOWED_TAGS, ['tbody']);\n        delete FORBID_TAGS.tbody;\n      }\n      if (cfg.TRUSTED_TYPES_POLICY) {\n        if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.');\n        }\n        if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.');\n        }\n\n        // Overwrite existing TrustedTypes policy.\n        trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n\n        // Sign local variables required by `sanitize`.\n        emptyHTML = trustedTypesPolicy.createHTML('');\n      } else {\n        // Uninitialized policy, attempt to initialize the internal dompurify policy.\n        if (trustedTypesPolicy === undefined) {\n          trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);\n        }\n\n        // If creating the internal policy succeeded sign internal variables.\n        if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {\n          emptyHTML = trustedTypesPolicy.createHTML('');\n        }\n      }\n\n      // Prevent further manipulation of configuration.\n      // Not available in IE8, Safari 5, etc.\n      if (freeze) {\n        freeze(cfg);\n      }\n      CONFIG = cfg;\n    };\n    const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);\n    const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);\n\n    // Certain elements are allowed in both SVG and HTML\n    // namespace. We need to specify them explicitly\n    // so that they don't get erroneously deleted from\n    // HTML namespace.\n    const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);\n\n    /* Keep track of all possible SVG and MathML tags\n     * so that we can perform the namespace checks\n     * correctly. */\n    const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);\n    const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);\n\n    /**\n     * @param  {Element} element a DOM element whose namespace is being checked\n     * @returns {boolean} Return false if the element has a\n     *  namespace that a spec-compliant parser would never\n     *  return. Return true otherwise.\n     */\n    const _checkValidNamespace = function _checkValidNamespace(element) {\n      let parent = getParentNode(element);\n\n      // In JSDOM, if we're inside shadow DOM, then parentNode\n      // can be null. We just simulate parent in this case.\n      if (!parent || !parent.tagName) {\n        parent = {\n          namespaceURI: NAMESPACE,\n          tagName: 'template'\n        };\n      }\n      const tagName = stringToLowerCase(element.tagName);\n      const parentTagName = stringToLowerCase(parent.tagName);\n      if (!ALLOWED_NAMESPACES[element.namespaceURI]) {\n        return false;\n      }\n      if (element.namespaceURI === SVG_NAMESPACE) {\n        // The only way to switch from HTML namespace to SVG\n        // is via <svg>. If it happens via any other tag, then\n        // it should be killed.\n        if (parent.namespaceURI === HTML_NAMESPACE) {\n          return tagName === 'svg';\n        }\n\n        // The only way to switch from MathML to SVG is via`\n        // svg if parent is either <annotation-xml> or MathML\n        // text integration points.\n        if (parent.namespaceURI === MATHML_NAMESPACE) {\n          return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);\n        }\n\n        // We only allow elements that are defined in SVG\n        // spec. All others are disallowed in SVG namespace.\n        return Boolean(ALL_SVG_TAGS[tagName]);\n      }\n      if (element.namespaceURI === MATHML_NAMESPACE) {\n        // The only way to switch from HTML namespace to MathML\n        // is via <math>. If it happens via any other tag, then\n        // it should be killed.\n        if (parent.namespaceURI === HTML_NAMESPACE) {\n          return tagName === 'math';\n        }\n\n        // The only way to switch from SVG to MathML is via\n        // <math> and HTML integration points\n        if (parent.namespaceURI === SVG_NAMESPACE) {\n          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];\n        }\n\n        // We only allow elements that are defined in MathML\n        // spec. All others are disallowed in MathML namespace.\n        return Boolean(ALL_MATHML_TAGS[tagName]);\n      }\n      if (element.namespaceURI === HTML_NAMESPACE) {\n        // The only way to switch from SVG to HTML is via\n        // HTML integration points, and from MathML to HTML\n        // is via MathML text integration points\n        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {\n          return false;\n        }\n        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {\n          return false;\n        }\n\n        // We disallow tags that are specific for MathML\n        // or SVG and should never appear in HTML namespace\n        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);\n      }\n\n      // For XHTML and XML documents that support custom namespaces\n      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {\n        return true;\n      }\n\n      // The code should never reach this place (this means\n      // that the element somehow got namespace that is not\n      // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).\n      // Return false just in case.\n      return false;\n    };\n\n    /**\n     * _forceRemove\n     *\n     * @param  {Node} node a DOM node\n     */\n    const _forceRemove = function _forceRemove(node) {\n      arrayPush(DOMPurify.removed, {\n        element: node\n      });\n      try {\n        // eslint-disable-next-line unicorn/prefer-dom-node-remove\n        getParentNode(node).removeChild(node);\n      } catch (_) {\n        remove(node);\n      }\n    };\n\n    /**\n     * _removeAttribute\n     *\n     * @param  {String} name an Attribute name\n     * @param  {Node} node a DOM node\n     */\n    const _removeAttribute = function _removeAttribute(name, node) {\n      try {\n        arrayPush(DOMPurify.removed, {\n          attribute: node.getAttributeNode(name),\n          from: node\n        });\n      } catch (_) {\n        arrayPush(DOMPurify.removed, {\n          attribute: null,\n          from: node\n        });\n      }\n      node.removeAttribute(name);\n\n      // We void attribute values for unremovable \"is\"\" attributes\n      if (name === 'is' && !ALLOWED_ATTR[name]) {\n        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {\n          try {\n            _forceRemove(node);\n          } catch (_) {}\n        } else {\n          try {\n            node.setAttribute(name, '');\n          } catch (_) {}\n        }\n      }\n    };\n\n    /**\n     * _initDocument\n     *\n     * @param  {String} dirty a string of dirty markup\n     * @return {Document} a DOM, filled with the dirty markup\n     */\n    const _initDocument = function _initDocument(dirty) {\n      /* Create a HTML document */\n      let doc = null;\n      let leadingWhitespace = null;\n      if (FORCE_BODY) {\n        dirty = '<remove></remove>' + dirty;\n      } else {\n        /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */\n        const matches = stringMatch(dirty, /^[\\r\\n\\t ]+/);\n        leadingWhitespace = matches && matches[0];\n      }\n      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {\n        // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)\n        dirty = '<html xmlns=\"http://www.w3.org/1999/xhtml\"><head></head><body>' + dirty + '</body></html>';\n      }\n      const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;\n      /*\n       * Use the DOMParser API by default, fallback later if needs be\n       * DOMParser not work for svg when has multiple root element.\n       */\n      if (NAMESPACE === HTML_NAMESPACE) {\n        try {\n          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);\n        } catch (_) {}\n      }\n\n      /* Use createHTMLDocument in case DOMParser is not available */\n      if (!doc || !doc.documentElement) {\n        doc = implementation.createDocument(NAMESPACE, 'template', null);\n        try {\n          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;\n        } catch (_) {\n          // Syntax error if dirtyPayload is invalid xml\n        }\n      }\n      const body = doc.body || doc.documentElement;\n      if (dirty && leadingWhitespace) {\n        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);\n      }\n\n      /* Work on whole document or just its body */\n      if (NAMESPACE === HTML_NAMESPACE) {\n        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];\n      }\n      return WHOLE_DOCUMENT ? doc.documentElement : body;\n    };\n\n    /**\n     * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.\n     *\n     * @param  {Node} root The root element or node to start traversing on.\n     * @return {NodeIterator} The created NodeIterator\n     */\n    const _createNodeIterator = function _createNodeIterator(root) {\n      return createNodeIterator.call(root.ownerDocument || root, root,\n      // eslint-disable-next-line no-bitwise\n      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);\n    };\n\n    /**\n     * _isClobbered\n     *\n     * @param  {Node} elm element to check for clobbering attacks\n     * @return {Boolean} true if clobbered, false if safe\n     */\n    const _isClobbered = function _isClobbered(elm) {\n      return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');\n    };\n\n    /**\n     * Checks whether the given object is a DOM node.\n     *\n     * @param  {Node} object object to check whether it's a DOM node\n     * @return {Boolean} true is object is a DOM node\n     */\n    const _isNode = function _isNode(object) {\n      return typeof Node === 'function' && object instanceof Node;\n    };\n\n    /**\n     * _executeHook\n     * Execute user configurable hooks\n     *\n     * @param  {String} entryPoint  Name of the hook's entry point\n     * @param  {Node} currentNode node to work on with the hook\n     * @param  {Object} data additional hook parameters\n     */\n    const _executeHook = function _executeHook(entryPoint, currentNode, data) {\n      if (!hooks[entryPoint]) {\n        return;\n      }\n      arrayForEach(hooks[entryPoint], hook => {\n        hook.call(DOMPurify, currentNode, data, CONFIG);\n      });\n    };\n\n    /**\n     * _sanitizeElements\n     *\n     * @protect nodeName\n     * @protect textContent\n     * @protect removeChild\n     *\n     * @param   {Node} currentNode to check for permission to exist\n     * @return  {Boolean} true if node was killed, false if left alive\n     */\n    const _sanitizeElements = function _sanitizeElements(currentNode) {\n      let content = null;\n\n      /* Execute a hook if present */\n      _executeHook('beforeSanitizeElements', currentNode, null);\n\n      /* Check if element is clobbered or can clobber */\n      if (_isClobbered(currentNode)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Now let's check the element's type and name */\n      const tagName = transformCaseFunc(currentNode.nodeName);\n\n      /* Execute a hook if present */\n      _executeHook('uponSanitizeElement', currentNode, {\n        tagName,\n        allowedTags: ALLOWED_TAGS\n      });\n\n      /* Detect mXSS attempts abusing namespace confusion */\n      if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\\w]/g, currentNode.innerHTML) && regExpTest(/<[/\\w]/g, currentNode.textContent)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Remove any occurrence of processing instructions */\n      if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Remove any kind of possibly harmful comments */\n      if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\\w]/g, currentNode.data)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Remove element if anything forbids its presence */\n      if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n        /* Check if we have a custom element to handle */\n        if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {\n          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {\n            return false;\n          }\n          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {\n            return false;\n          }\n        }\n\n        /* Keep content except for bad-listed elements */\n        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {\n          const parentNode = getParentNode(currentNode) || currentNode.parentNode;\n          const childNodes = getChildNodes(currentNode) || currentNode.childNodes;\n          if (childNodes && parentNode) {\n            const childCount = childNodes.length;\n            for (let i = childCount - 1; i >= 0; --i) {\n              const childClone = cloneNode(childNodes[i], true);\n              childClone.__removalCount = (currentNode.__removalCount || 0) + 1;\n              parentNode.insertBefore(childClone, getNextSibling(currentNode));\n            }\n          }\n        }\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Check whether element has a valid namespace */\n      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Make sure that older browsers don't get fallback-tag mXSS */\n      if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\\/no(script|embed|frames)/i, currentNode.innerHTML)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n\n      /* Sanitize element content to be template-safe */\n      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {\n        /* Get the element's text content */\n        content = currentNode.textContent;\n        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {\n          content = stringReplace(content, expr, ' ');\n        });\n        if (currentNode.textContent !== content) {\n          arrayPush(DOMPurify.removed, {\n            element: currentNode.cloneNode()\n          });\n          currentNode.textContent = content;\n        }\n      }\n\n      /* Execute a hook if present */\n      _executeHook('afterSanitizeElements', currentNode, null);\n      return false;\n    };\n\n    /**\n     * _isValidAttribute\n     *\n     * @param  {string} lcTag Lowercase tag name of containing element.\n     * @param  {string} lcName Lowercase attribute name.\n     * @param  {string} value Attribute value.\n     * @return {Boolean} Returns true if `value` is valid, otherwise false.\n     */\n    // eslint-disable-next-line complexity\n    const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {\n      /* Make sure attribute cannot clobber */\n      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {\n        return false;\n      }\n\n      /* Allow valid data-* attributes: At least one character after \"-\"\n          (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)\n          XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)\n          We don't need to check the value; it's always URI safe. */\n      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {\n        if (\n        // First condition does a very basic check if a) it's basically a valid custom element tagname AND\n        // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n        // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck\n        _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||\n        // Alternative, second condition checks if it's an `is`-attribute, AND\n        // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n        lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {\n          return false;\n        }\n        /* Check value is safe. First, is attr inert? If so, is safe */\n      } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {\n        return false;\n      } else ;\n      return true;\n    };\n\n    /**\n     * _isBasicCustomElement\n     * checks if at least one dash is included in tagName, and it's not the first char\n     * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name\n     *\n     * @param {string} tagName name of the tag of the node to sanitize\n     * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.\n     */\n    const _isBasicCustomElement = function _isBasicCustomElement(tagName) {\n      return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);\n    };\n\n    /**\n     * _sanitizeAttributes\n     *\n     * @protect attributes\n     * @protect nodeName\n     * @protect removeAttribute\n     * @protect setAttribute\n     *\n     * @param  {Node} currentNode to sanitize\n     */\n    const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {\n      /* Execute a hook if present */\n      _executeHook('beforeSanitizeAttributes', currentNode, null);\n      const {\n        attributes\n      } = currentNode;\n\n      /* Check if we have attributes; if not we might have a text node */\n      if (!attributes) {\n        return;\n      }\n      const hookEvent = {\n        attrName: '',\n        attrValue: '',\n        keepAttr: true,\n        allowedAttributes: ALLOWED_ATTR\n      };\n      let l = attributes.length;\n\n      /* Go backwards over all attributes; safely remove bad ones */\n      while (l--) {\n        const attr = attributes[l];\n        const {\n          name,\n          namespaceURI,\n          value: attrValue\n        } = attr;\n        const lcName = transformCaseFunc(name);\n        let value = name === 'value' ? attrValue : stringTrim(attrValue);\n\n        /* Execute a hook if present */\n        hookEvent.attrName = lcName;\n        hookEvent.attrValue = value;\n        hookEvent.keepAttr = true;\n        hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set\n        _executeHook('uponSanitizeAttribute', currentNode, hookEvent);\n        value = hookEvent.attrValue;\n\n        /* Did the hooks approve of the attribute? */\n        if (hookEvent.forceKeepAttr) {\n          continue;\n        }\n\n        /* Remove attribute */\n        _removeAttribute(name, currentNode);\n\n        /* Did the hooks approve of the attribute? */\n        if (!hookEvent.keepAttr) {\n          continue;\n        }\n\n        /* Work around a security issue in jQuery 3.0 */\n        if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\\/>/i, value)) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n\n        /* Sanitize attribute content to be template-safe */\n        if (SAFE_FOR_TEMPLATES) {\n          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {\n            value = stringReplace(value, expr, ' ');\n          });\n        }\n\n        /* Is `value` valid for this attribute? */\n        const lcTag = transformCaseFunc(currentNode.nodeName);\n        if (!_isValidAttribute(lcTag, lcName, value)) {\n          continue;\n        }\n\n        /* Full DOM Clobbering protection via namespace isolation,\n         * Prefix id and name attributes with `user-content-`\n         */\n        if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {\n          // Remove the attribute with this value\n          _removeAttribute(name, currentNode);\n\n          // Prefix the value and later re-create the attribute with the sanitized value\n          value = SANITIZE_NAMED_PROPS_PREFIX + value;\n        }\n\n        /* Work around a security issue with comments inside attributes */\n        if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\\/(style|title)/i, value)) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n\n        /* Handle attributes that require Trusted Types */\n        if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {\n          if (namespaceURI) ; else {\n            switch (trustedTypes.getAttributeType(lcTag, lcName)) {\n              case 'TrustedHTML':\n                {\n                  value = trustedTypesPolicy.createHTML(value);\n                  break;\n                }\n              case 'TrustedScriptURL':\n                {\n                  value = trustedTypesPolicy.createScriptURL(value);\n                  break;\n                }\n            }\n          }\n        }\n\n        /* Handle invalid data-* attribute set by try-catching it */\n        try {\n          if (namespaceURI) {\n            currentNode.setAttributeNS(namespaceURI, name, value);\n          } else {\n            /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. \"x-schema\". */\n            currentNode.setAttribute(name, value);\n          }\n          if (_isClobbered(currentNode)) {\n            _forceRemove(currentNode);\n          } else {\n            arrayPop(DOMPurify.removed);\n          }\n        } catch (_) {}\n      }\n\n      /* Execute a hook if present */\n      _executeHook('afterSanitizeAttributes', currentNode, null);\n    };\n\n    /**\n     * _sanitizeShadowDOM\n     *\n     * @param  {DocumentFragment} fragment to iterate over recursively\n     */\n    const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {\n      let shadowNode = null;\n      const shadowIterator = _createNodeIterator(fragment);\n\n      /* Execute a hook if present */\n      _executeHook('beforeSanitizeShadowDOM', fragment, null);\n      while (shadowNode = shadowIterator.nextNode()) {\n        /* Execute a hook if present */\n        _executeHook('uponSanitizeShadowNode', shadowNode, null);\n\n        /* Sanitize tags and elements */\n        if (_sanitizeElements(shadowNode)) {\n          continue;\n        }\n\n        /* Deep shadow DOM detected */\n        if (shadowNode.content instanceof DocumentFragment) {\n          _sanitizeShadowDOM(shadowNode.content);\n        }\n\n        /* Check attributes, sanitize if necessary */\n        _sanitizeAttributes(shadowNode);\n      }\n\n      /* Execute a hook if present */\n      _executeHook('afterSanitizeShadowDOM', fragment, null);\n    };\n\n    /**\n     * Sanitize\n     * Public method providing core sanitation functionality\n     *\n     * @param {String|Node} dirty string or DOM node\n     * @param {Object} cfg object\n     */\n    // eslint-disable-next-line complexity\n    DOMPurify.sanitize = function (dirty) {\n      let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let body = null;\n      let importedNode = null;\n      let currentNode = null;\n      let returnNode = null;\n      /* Make sure we have a string to sanitize.\n        DO NOT return early, as this will return the wrong type if\n        the user has requested a DOM object rather than a string */\n      IS_EMPTY_INPUT = !dirty;\n      if (IS_EMPTY_INPUT) {\n        dirty = '<!-->';\n      }\n\n      /* Stringify, in case dirty is an object */\n      if (typeof dirty !== 'string' && !_isNode(dirty)) {\n        if (typeof dirty.toString === 'function') {\n          dirty = dirty.toString();\n          if (typeof dirty !== 'string') {\n            throw typeErrorCreate('dirty is not a string, aborting');\n          }\n        } else {\n          throw typeErrorCreate('toString is not a function');\n        }\n      }\n\n      /* Return dirty HTML if DOMPurify cannot run */\n      if (!DOMPurify.isSupported) {\n        return dirty;\n      }\n\n      /* Assign config vars */\n      if (!SET_CONFIG) {\n        _parseConfig(cfg);\n      }\n\n      /* Clean up removed elements */\n      DOMPurify.removed = [];\n\n      /* Check if dirty is correctly typed for IN_PLACE */\n      if (typeof dirty === 'string') {\n        IN_PLACE = false;\n      }\n      if (IN_PLACE) {\n        /* Do some early pre-sanitization to avoid unsafe root nodes */\n        if (dirty.nodeName) {\n          const tagName = transformCaseFunc(dirty.nodeName);\n          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');\n          }\n        }\n      } else if (dirty instanceof Node) {\n        /* If dirty is a DOM element, append to an empty document to avoid\n           elements being stripped by the parser */\n        body = _initDocument('<!---->');\n        importedNode = body.ownerDocument.importNode(dirty, true);\n        if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {\n          /* Node is already a body, use as is */\n          body = importedNode;\n        } else if (importedNode.nodeName === 'HTML') {\n          body = importedNode;\n        } else {\n          // eslint-disable-next-line unicorn/prefer-dom-node-append\n          body.appendChild(importedNode);\n        }\n      } else {\n        /* Exit directly if we have nothing to do */\n        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&\n        // eslint-disable-next-line unicorn/prefer-includes\n        dirty.indexOf('<') === -1) {\n          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;\n        }\n\n        /* Initialize the document to work on */\n        body = _initDocument(dirty);\n\n        /* Check we have a DOM node from the data */\n        if (!body) {\n          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';\n        }\n      }\n\n      /* Remove first element node (ours) if FORCE_BODY is set */\n      if (body && FORCE_BODY) {\n        _forceRemove(body.firstChild);\n      }\n\n      /* Get node iterator */\n      const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);\n\n      /* Now start iterating over the created document */\n      while (currentNode = nodeIterator.nextNode()) {\n        /* Sanitize tags and elements */\n        if (_sanitizeElements(currentNode)) {\n          continue;\n        }\n\n        /* Shadow DOM detected, sanitize it */\n        if (currentNode.content instanceof DocumentFragment) {\n          _sanitizeShadowDOM(currentNode.content);\n        }\n\n        /* Check attributes, sanitize if necessary */\n        _sanitizeAttributes(currentNode);\n      }\n\n      /* If we sanitized `dirty` in-place, return it. */\n      if (IN_PLACE) {\n        return dirty;\n      }\n\n      /* Return sanitized string or DOM */\n      if (RETURN_DOM) {\n        if (RETURN_DOM_FRAGMENT) {\n          returnNode = createDocumentFragment.call(body.ownerDocument);\n          while (body.firstChild) {\n            // eslint-disable-next-line unicorn/prefer-dom-node-append\n            returnNode.appendChild(body.firstChild);\n          }\n        } else {\n          returnNode = body;\n        }\n        if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {\n          /*\n            AdoptNode() is not used because internal state is not reset\n            (e.g. the past names map of a HTMLFormElement), this is safe\n            in theory but we would rather not risk another attack vector.\n            The state that is cloned by importNode() is explicitly defined\n            by the specs.\n          */\n          returnNode = importNode.call(originalDocument, returnNode, true);\n        }\n        return returnNode;\n      }\n      let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;\n\n      /* Serialize doctype if allowed */\n      if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {\n        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\\n' + serializedHTML;\n      }\n\n      /* Sanitize final string template-safe */\n      if (SAFE_FOR_TEMPLATES) {\n        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {\n          serializedHTML = stringReplace(serializedHTML, expr, ' ');\n        });\n      }\n      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;\n    };\n\n    /**\n     * Public method to set the configuration once\n     * setConfig\n     *\n     * @param {Object} cfg configuration object\n     */\n    DOMPurify.setConfig = function () {\n      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      _parseConfig(cfg);\n      SET_CONFIG = true;\n    };\n\n    /**\n     * Public method to remove the configuration\n     * clearConfig\n     *\n     */\n    DOMPurify.clearConfig = function () {\n      CONFIG = null;\n      SET_CONFIG = false;\n    };\n\n    /**\n     * Public method to check if an attribute value is valid.\n     * Uses last set config, if any. Otherwise, uses config defaults.\n     * isValidAttribute\n     *\n     * @param  {String} tag Tag name of containing element.\n     * @param  {String} attr Attribute name.\n     * @param  {String} value Attribute value.\n     * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.\n     */\n    DOMPurify.isValidAttribute = function (tag, attr, value) {\n      /* Initialize shared config vars if necessary. */\n      if (!CONFIG) {\n        _parseConfig({});\n      }\n      const lcTag = transformCaseFunc(tag);\n      const lcName = transformCaseFunc(attr);\n      return _isValidAttribute(lcTag, lcName, value);\n    };\n\n    /**\n     * AddHook\n     * Public method to add DOMPurify hooks\n     *\n     * @param {String} entryPoint entry point for the hook to add\n     * @param {Function} hookFunction function to execute\n     */\n    DOMPurify.addHook = function (entryPoint, hookFunction) {\n      if (typeof hookFunction !== 'function') {\n        return;\n      }\n      hooks[entryPoint] = hooks[entryPoint] || [];\n      arrayPush(hooks[entryPoint], hookFunction);\n    };\n\n    /**\n     * RemoveHook\n     * Public method to remove a DOMPurify hook at a given entryPoint\n     * (pops it from the stack of hooks if more are present)\n     *\n     * @param {String} entryPoint entry point for the hook to remove\n     * @return {Function} removed(popped) hook\n     */\n    DOMPurify.removeHook = function (entryPoint) {\n      if (hooks[entryPoint]) {\n        return arrayPop(hooks[entryPoint]);\n      }\n    };\n\n    /**\n     * RemoveHooks\n     * Public method to remove all DOMPurify hooks at a given entryPoint\n     *\n     * @param  {String} entryPoint entry point for the hooks to remove\n     */\n    DOMPurify.removeHooks = function (entryPoint) {\n      if (hooks[entryPoint]) {\n        hooks[entryPoint] = [];\n      }\n    };\n\n    /**\n     * RemoveAllHooks\n     * Public method to remove all DOMPurify hooks\n     */\n    DOMPurify.removeAllHooks = function () {\n      hooks = {};\n    };\n    return DOMPurify;\n  }\n  var purify = createDOMPurify();\n\n  return purify;\n\n}));\n//# sourceMappingURL=purify.js.map\n", "import { useEffect, useState } from \"@odoo/owl\";\n\nexport function useDropdownAutoVisibility(overlayState, popoverRef) {\n    if (!overlayState) {\n        return;\n    }\n    const state = useState(overlayState);\n    useEffect(\n        () => {\n            if (popoverRef.el) {\n                if (!state.isOverlayVisible) {\n                    popoverRef.el.style.visibility = \"hidden\";\n                } else {\n                    popoverRef.el.style.visibility = \"visible\";\n                }\n            }\n        },\n        () => [state.isOverlayVisible]\n    );\n}\n", "import { MAIN_PLUGINS } from \"./plugin_sets\";\nimport { createBaseContainer, SUPPORTED_BASE_CONTAINER_NAMES } from \"./utils/base_container\";\nimport { fillShrunkPhrasingParent, removeClass } from \"./utils/dom\";\nimport { isEmpty } from \"./utils/dom_info\";\nimport { resourceSequenceSymbol, withSequence } from \"./utils/resource\";\nimport { fixInvalidHTML, initElementForEdition } from \"./utils/sanitize\";\nimport { setElementContent } from \"@web/core/utils/html\";\n\n/** @typedef {import(\"plugins\").EditorResources} EditorResources */\n/** @typedef {import(\"plugins\").GlobalResources} GlobalResources */\n/** @typedef {keyof GlobalResources} GlobalResourcesId */\n/**\n * @typedef {import(\"plugins\").SharedMethods} SharedMethods\n * @typedef {import(\"plugins\").PluginConstructor} PluginConstructor\n **/\n\n/**\n * @typedef { Object } CollaborationConfig\n * @property { string } collaboration.peerId\n * @property { Object } collaboration.busService\n * @property { Object } collaboration.collaborationChannel\n * @property { String } collaboration.collaborationChannel.collaborationModelName\n * @property { String } collaboration.collaborationChannel.collaborationFieldName\n * @property { Number } collaboration.collaborationChannel.collaborationResId\n * @property { 'start' | 'focus' } [collaboration.collaborativeTrigger]\n\n * @typedef { Object } EditorConfig\n * @property { string } [content]\n * @property { boolean } [allowInlineAtRoot]\n * @property { string[] } [baseContainers]\n * @property { PluginConstructor[] } [Plugins]\n * @property { string[] } [classList]\n * @property { Object } [localOverlayContainers]\n * @property { Object } [embeddedComponentInfo]\n * @property { Object } [resources]\n * @property { string } [direction=\"ltr\"]\n * @property { Function } [onChange]\n * @property { Function } [onEditorReady]\n * @property { boolean } [dropImageAsAttachment]\n * @property { CollaborationConfig } [collaboration]\n * @property { Function } getRecordInfo\n *\n * @typedef { Object } EditorContext\n * @property { Document } document\n * @property { HTMLElement } editable\n * @property { SharedMethods } dependencies\n * @property { import(\"./editor\").EditorConfig } config\n * @property { import(\"services\").ServiceFactories } services\n * @property { Editor['getResource'] } getResource\n * @property { Editor['dispatchTo'] } dispatchTo\n * @property { Editor['delegateTo'] } delegateTo\n */\n\n/**\n * @typedef {((arg: {root: EditorContext[\"editable\"]}) => void)[]} clean_for_save_handlers\n * @typedef {(() => void)[]} start_edition_handlers\n */\n\n/**\n * Clean up DOM before taking into account for next history step remaining in\n * edit mode\n * @typedef {((root: EditorContext[\"editable\"] | HTMLElement) => void)[]} normalize_handlers\n */\n\n/**\n * @param {PluginConstructor[]} plugins\n * @returns {PluginConstructor[]}\n */\nfunction sortPlugins(plugins) {\n    const initialPlugins = new Set(plugins);\n    const inResult = new Set();\n    // need to sort them\n    const result = [];\n    let P;\n\n    function findPlugin() {\n        for (const P of initialPlugins) {\n            if (P.dependencies.every((dep) => inResult.has(dep))) {\n                initialPlugins.delete(P);\n                return P;\n            }\n        }\n    }\n    while ((P = findPlugin())) {\n        inResult.add(P.id);\n        result.push(P);\n    }\n    if (initialPlugins.size) {\n        const messages = [];\n        for (const P of initialPlugins) {\n            messages.push(\n                `\"${P.id}\" is missing (${P.dependencies\n                    .filter((d) => !inResult.has(d))\n                    .join(\", \")})`\n            );\n        }\n        throw new Error(`Missing dependencies:  ${messages.join(\", \")}`);\n    }\n    return result;\n}\n\nexport class Editor {\n    /**\n     * @param { EditorConfig } config\n     */\n    constructor(config, services) {\n        this.isReady = false;\n        this.isDestroyed = false;\n        this.config = config;\n        this.services = services;\n        /** @type { EditorResources } */\n        this.resources = null;\n        this.plugins = [];\n        /** @type { HTMLElement } **/\n        this.editable = null;\n        /** @type { Document } **/\n        this.document = null;\n        /** @ts-ignore  @type { SharedMethods } **/\n        this.shared = {};\n    }\n\n    attachTo(editable) {\n        if (this.isDestroyed || this.editable) {\n            throw new Error(\"Cannot re-attach an editor\");\n        }\n        this.editable = editable;\n        this.document = editable.ownerDocument;\n        this.preparePlugins();\n        if (\"content\" in this.config) {\n            setElementContent(editable, fixInvalidHTML(this.config.content));\n            if (isEmpty(editable)) {\n                const baseContainer = createBaseContainer(\n                    this.config.baseContainers[0],\n                    this.document\n                );\n                fillShrunkPhrasingParent(baseContainer);\n                editable.replaceChildren(baseContainer);\n            }\n        }\n        editable.setAttribute(\"contenteditable\", true);\n        initElementForEdition(editable, { allowInlineAtRoot: !!this.config.allowInlineAtRoot });\n        editable.classList.add(\"odoo-editor-editable\");\n        if (this.config.classList) {\n            editable.classList.add(...this.config.classList);\n        }\n        if (this.config.height) {\n            editable.style.height = this.config.height;\n        }\n        if (\n            !this.config.baseContainers.every((name) =>\n                SUPPORTED_BASE_CONTAINER_NAMES.includes(name)\n            )\n        ) {\n            throw new Error(\n                `Invalid baseContainers: ${this.config.baseContainers.join(\n                    \", \"\n                )}. Supported: ${SUPPORTED_BASE_CONTAINER_NAMES.join(\", \")}`\n            );\n        }\n        this.startPlugins();\n        this.isReady = true;\n        this.config.onEditorReady?.();\n    }\n\n    preparePlugins() {\n        const Plugins = sortPlugins(this.config.Plugins || MAIN_PLUGINS);\n        this.config = Object.assign({}, ...Plugins.map((P) => P.defaultConfig), this.config);\n        this.pluginsMap = new Map();\n        for (const P of Plugins) {\n            if (P.id === \"\") {\n                throw new Error(`Missing plugin id (class ${P.name})`);\n            }\n            if (this.pluginsMap.has(P.id)) {\n                throw new Error(`Duplicate plugin id: ${P.id}`);\n            }\n            this.pluginsMap.set(P.id, P);\n            const plugin = new P(this.getEditorContext(P.dependencies));\n            plugin.__editor = this;\n            this.plugins.push(plugin);\n            const exports = {};\n            for (const h of P.shared) {\n                if (!(h in plugin)) {\n                    throw new Error(`Missing helper implementation: ${h} in plugin ${P.id}`);\n                }\n                exports[h] = plugin[h].bind(plugin);\n            }\n            this.shared[P.id] = exports;\n        }\n        const resources = this.createResources();\n        for (const plugin of this.plugins) {\n            plugin._resources = resources;\n        }\n        this.resources = resources;\n    }\n\n    startPlugins() {\n        for (const plugin of this.plugins) {\n            plugin.setup();\n        }\n        this.resources[\"normalize_handlers\"].forEach((cb) => cb(this.editable));\n        this.resources[\"start_edition_handlers\"].forEach((cb) => cb());\n    }\n\n    getDependencies(dependencies) {\n        const deps = {};\n        for (const depName of dependencies) {\n            if (!(depName in this.shared)) {\n                throw new Error(`Missing dependency: ${depName}`);\n            }\n            deps[depName] = this.shared[depName];\n        }\n        return deps;\n    }\n\n    createResources() {\n        const resources = {};\n\n        function registerResources(obj) {\n            for (const key in obj) {\n                if (!(key in resources)) {\n                    resources[key] = [];\n                }\n                resources[key].push(obj[key]);\n            }\n        }\n        if (this.config.resources) {\n            registerResources(this.config.resources);\n        }\n        for (const plugin of this.plugins) {\n            if (plugin.resources) {\n                registerResources(plugin.resources);\n            }\n        }\n\n        for (const key in resources) {\n            const resource = resources[key]\n                .flat()\n                .map((r) => {\n                    const isObjectWithSequence =\n                        typeof r === \"object\" && r !== null && resourceSequenceSymbol in r;\n                    return isObjectWithSequence ? r : withSequence(10, r);\n                })\n                .sort((a, b) => a[resourceSequenceSymbol] - b[resourceSequenceSymbol])\n                .map((r) => r.object);\n\n            resources[key] = resource;\n            Object.freeze(resources[key]);\n        }\n\n        return Object.freeze(resources);\n    }\n\n    /**\n     * @return { EditorContext }\n     */\n    getEditorContext(dependencies = []) {\n        return {\n            document: this.document,\n            editable: this.editable,\n            dependencies: this.getDependencies(dependencies),\n            config: this.config,\n            services: this.services,\n            getResource: this.getResource.bind(this),\n            dispatchTo: this.dispatchTo.bind(this),\n            delegateTo: this.delegateTo.bind(this),\n        };\n    }\n\n    /**\n     * @template {GlobalResourcesId} R\n     * @param {R} resourceId\n     * @returns {GlobalResources[R]}\n     */\n    getResource(resourceId) {\n        return this.resources[resourceId] || [];\n    }\n\n    /**\n     * Execute the functions registered under resourceId with the given\n     * arguments.\n     *\n     * This function is meant to enhance code readability by clearly expressing\n     * its intent.\n     *\n     * This function can be thought as an event dispatcher, calling the handlers\n     * with `args` as the payload.\n     *\n     * Example:\n     * ```js\n     * this.dispatchTo(\"my_event_handlers\", arg1, arg2);\n     * ```\n     *\n     * @template {GlobalResourcesId} R\n     * @param {R} resourceId\n     * @param {Parameters<GlobalResources[R][0]>} args The arguments to pass to the handlers.\n     */\n    dispatchTo(resourceId, ...args) {\n        this.getResource(resourceId).forEach((handler) => handler(...args));\n    }\n    /**\n     * Execute a series of functions until one of them returns a truthy value.\n     *\n     * This function is meant to enhance code readability by clearly expressing\n     * its intent.\n     *\n     * A command \"delegates\" its execution to one of the overriding functions,\n     * which return a truthy value to signal it has been handled.\n     *\n     * It is the the caller's responsability to stop the execution when this\n     * function returns true.\n     *\n     * Example:\n     * ```js\n     * if (this.delegateTo(\"my_command_overrides\", arg1, arg2)) {\n     *   return;\n     * }\n     * ```\n     *\n     * @template {GlobalResourcesId} R\n     * @param {R} resourceId\n     * @param {Parameters<GlobalResources[R][0]>} args The arguments to pass to the overrides.\n     * @returns {boolean} Whether one of the overrides returned a truthy value.\n     */\n    delegateTo(resourceId, ...args) {\n        return this.getResource(resourceId).some((fn) => fn(...args));\n    }\n\n    getContent() {\n        return this.getElContent().innerHTML;\n    }\n\n    getElContent() {\n        const el = this.editable.cloneNode(true);\n        this.resources[\"clean_for_save_handlers\"].forEach((cb) => cb({ root: el }));\n        return el;\n    }\n\n    destroy(willBeRemoved) {\n        if (this.editable) {\n            let plugin;\n            while ((plugin = this.plugins.pop())) {\n                plugin.destroy();\n            }\n            this.shared = {};\n            if (!willBeRemoved) {\n                // we only remove class/attributes when necessary. If we know that the editable\n                // element will be removed, no need to make changes that may require the browser\n                // to recompute the layout\n                this.editable.removeAttribute(\"contenteditable\");\n                removeClass(this.editable, \"odoo-editor-editable\");\n            }\n            this.editable = null;\n        }\n        this.isDestroyed = true;\n    }\n}\n", "import { isProtected, isProtecting, isUnprotecting } from \"./utils/dom_info\";\n\nexport const isValidTargetForDomListener = (target) =>\n    !isProtecting(target) && (!isProtected(target) || isUnprotecting(target));\n\n/**\n * @typedef { import(\"./editor\").Editor } Editor\n * @typedef { import(\"./editor\").EditorContext } EditorContext\n */\n\nexport class Plugin {\n    static id = \"\";\n    static dependencies = [];\n    static shared = [];\n    static defaultConfig = {};\n\n    /** @type {Partial<import(\"plugins\").Resources>} */\n    resources;\n\n    /**\n     * @param { EditorContext } context\n     */\n    constructor(context) {\n        /** @type { EditorContext['document'] } **/\n        this.document = context.document;\n        this.window = context.document.defaultView;\n        /** @type { EditorContext['editable'] } **/\n        this.editable = context.editable;\n        /** @type { EditorContext['config'] } **/\n        this.config = context.config;\n        /** @type { EditorContext['services'] } **/\n        this.services = context.services;\n        /** @type { EditorContext['dependencies'] } **/\n        this.dependencies = context.dependencies;\n        /** @type { EditorContext['getResource'] } **/\n        this.getResource = context.getResource;\n        /** @type { EditorContext['dispatchTo'] } **/\n        this.dispatchTo = context.dispatchTo;\n        /** @type { EditorContext['delegateTo'] } **/\n        this.delegateTo = context.delegateTo;\n\n        this._cleanups = [];\n        this.isDestroyed = false;\n    }\n\n    setup() {}\n\n    isValidTargetForDomListener(ev) {\n        return isValidTargetForDomListener(ev.target);\n    }\n\n    /**\n     * Add an event listener on a given target, that will only be executed if\n     * the target is valid (unless `isGlobal` is true), and ensure it is removed\n     * when we destroy the editor.\n     *\n     * @param {Element} target\n     * @param {string} eventName\n     * @param {function(Event):void} fn\n     * @param {boolean} [capture=false] `useCapture` flag of `addEventListener`\n     * @param {boolean} [isGlobal=false] if true, don't check target validity\n     */\n    addDomListener(target, eventName, fn, capture = false, isGlobal = false) {\n        const handler = (ev) => {\n            if (isGlobal || this.isValidTargetForDomListener(ev)) {\n                fn?.call(this, ev);\n            }\n        };\n        target.addEventListener(eventName, handler, capture);\n        this._cleanups.push(() => target.removeEventListener(eventName, handler, capture));\n    }\n\n    /**\n     * Add an event listener on the editor's document, and ensure it is removed\n     * when we destroy the editor.\n     *\n     * @todo Use this function to avoid iframe problems.\n     *\n     * @param {string} eventName\n     * @param {function(Event):void} fn\n     * @param {boolean} [capture=false] `useCapture` flag of `addEventListener`\n     */\n    addGlobalDomListener(eventName, fn, capture = false) {\n        this.addDomListener(this.document, eventName, fn, capture, true);\n    }\n\n    destroy() {\n        for (const cleanup of this._cleanups) {\n            cleanup();\n        }\n        this.isDestroyed = true;\n    }\n}\n", "import { BaseContainerPlugin } from \"./core/base_container_plugin\";\nimport { ClipboardPlugin } from \"./core/clipboard_plugin\";\nimport { CommentPlugin } from \"./core/comment_plugin\";\nimport { DeletePlugin } from \"./core/delete_plugin\";\nimport { DialogPlugin } from \"./core/dialog_plugin\";\nimport { DomPlugin } from \"./core/dom_plugin\";\nimport { SeparatorPlugin } from \"./main/separator_plugin\";\nimport { FormatPlugin } from \"./core/format_plugin\";\nimport { HistoryPlugin } from \"./core/history_plugin\";\nimport { InputPlugin } from \"./core/input_plugin\";\nimport { LineBreakPlugin } from \"./core/line_break_plugin\";\nimport { NoInlineRootPlugin } from \"./core/no_inline_root_plugin\";\nimport { OverlayPlugin } from \"./core/overlay_plugin\";\nimport { ProtectedNodePlugin } from \"./core/protected_node_plugin\";\nimport { SanitizePlugin } from \"./core/sanitize_plugin\";\nimport { SelectionPlugin } from \"./core/selection_plugin\";\nimport { ShortCutPlugin } from \"./core/shortcut_plugin\";\nimport { SplitPlugin } from \"./core/split_plugin\";\nimport { UserCommandPlugin } from \"./core/user_command_plugin\";\nimport { AlignPlugin } from \"./main/align/align_plugin\";\nimport { BannerPlugin } from \"./main/banner_plugin\";\nimport { ChatGPTTranslatePlugin } from \"./main/chatgpt/chatgpt_translate_plugin\";\nimport { ColumnPlugin } from \"./main/column_plugin\";\nimport { EmojiPlugin } from \"./main/emoji_plugin\";\nimport { ColorPlugin } from \"./main/font/color_plugin\";\nimport { ColorUIPlugin } from \"./main/font/color_ui_plugin\";\nimport { FeffPlugin } from \"./main/feff_plugin\";\nimport { FontPlugin } from \"./main/font/font_plugin\";\nimport { FontFamilyPlugin } from \"./main/font/font_family_plugin\";\nimport { HintPlugin } from \"./main/hint_plugin\";\nimport { InlineCodePlugin } from \"./main/inline_code\";\nimport { LinkPastePlugin } from \"./main/link/link_paste_plugin\";\nimport { LinkPlugin } from \"./main/link/link_plugin\";\nimport { OdooLinkSelectionPlugin } from \"./main/link/link_selection_odoo_plugin\";\nimport { LinkSelectionPlugin } from \"./main/link/link_selection_plugin\";\nimport { ListPlugin } from \"./main/list/list_plugin\";\nimport { LocalOverlayPlugin } from \"./main/local_overlay_plugin\";\nimport { FilePlugin } from \"./main/media/file_plugin\";\nimport { IconPlugin } from \"./main/media/icon_plugin\";\nimport { IconColorPlugin } from \"./main/media/icon_color_plugin\";\nimport { ImageCropPlugin } from \"./main/media/image_crop_plugin\";\nimport { ImagePlugin } from \"./main/media/image_plugin\";\nimport { ImageSavePlugin } from \"./main/media/image_save_plugin\";\nimport { MediaPlugin } from \"./main/media/media_plugin\";\nimport { MoveNodePlugin } from \"./main/movenode_plugin\";\nimport { PowerButtonsPlugin } from \"./main/power_buttons_plugin\";\nimport { PositionPlugin } from \"./main/position_plugin\";\nimport { PowerboxPlugin } from \"./main/powerbox/powerbox_plugin\";\nimport { MediaUrlPastePlugin } from \"./main/link/powerbox_url_paste_plugin\";\nimport { SearchPowerboxPlugin } from \"./main/powerbox/search_powerbox_plugin\";\nimport { StarPlugin } from \"./main/star_plugin\";\nimport { TableAlignPlugin } from \"./main/table/table_align_plugin\";\nimport { TablePlugin } from \"./main/table/table_plugin\";\nimport { TableResizePlugin } from \"./main/table/table_resize_plugin\";\nimport { TableUIPlugin } from \"./main/table/table_ui_plugin\";\nimport { TabulationPlugin } from \"./main/tabulation_plugin\";\nimport { TextDirectionPlugin } from \"./main/text_direction_plugin\";\nimport { ToolbarPlugin } from \"./main/toolbar/toolbar_plugin\";\nimport { VideoPlugin } from \"./main/media/video_plugin\";\nimport { YoutubePlugin } from \"./main/youtube_plugin\";\nimport { PlaceholderPlugin } from \"./main/placeholder_plugin\";\nimport { CollaborationOdooPlugin } from \"./others/collaboration/collaboration_odoo_plugin\";\nimport { CollaborationPlugin } from \"./others/collaboration/collaboration_plugin\";\nimport { CollaborationSelectionAvatarPlugin } from \"./others/collaboration/collaboration_selection_avatar_plugin\";\nimport { CollaborationSelectionPlugin } from \"./others/collaboration/collaboration_selection_plugin\";\nimport { EmbeddedComponentPlugin } from \"./others/embedded_component_plugin\";\nimport { TableOfContentPlugin } from \"@html_editor/others/embedded_components/plugins/table_of_content_plugin/table_of_content_plugin\";\nimport { ToggleBlockPlugin } from \"@html_editor/others/embedded_components/plugins/toggle_block_plugin/toggle_block_plugin\";\nimport { EmbeddedVideoPlugin } from \"@html_editor/others/embedded_components/plugins/video_plugin/embedded_video_plugin\";\nimport { EmbeddedYoutubePlugin } from \"./others/embedded_components/plugins/video_plugin/embedded_youtube_plugin\";\nimport { CaptionPlugin } from \"@html_editor/others/embedded_components/plugins/caption_plugin/caption_plugin\";\nimport { EmbeddedFilePlugin } from \"@html_editor/others/embedded_components/plugins/embedded_file_plugin/embedded_file_plugin\";\nimport { SyntaxHighlightingPlugin } from \"@html_editor/others/embedded_components/plugins/syntax_highlighting_plugin/syntax_highlighting_plugin\";\nimport { QWebPlugin } from \"./others/qweb_plugin\";\nimport { EditorVersionPlugin } from \"./core/editor_version_plugin\";\nimport { ImagePostProcessPlugin } from \"./main/media/image_post_process_plugin\";\nimport { DoubleClickImagePreviewPlugin } from \"./main/media/dblclick_image_preview_plugin\";\nimport { StylePlugin } from \"./core/style_plugin\";\nimport { ContentEditablePlugin } from \"./core/content_editable_plugin\";\nimport { SelectionPlaceholderPlugin } from \"./main/selection_placeholder_plugin\";\n\nexport const CORE_PLUGINS = [\n    BaseContainerPlugin,\n    ClipboardPlugin,\n    CommentPlugin,\n    DeletePlugin,\n    DialogPlugin,\n    DomPlugin,\n    FormatPlugin,\n    HistoryPlugin,\n    InputPlugin,\n    LineBreakPlugin,\n    NoInlineRootPlugin,\n    OverlayPlugin,\n    ProtectedNodePlugin,\n    SanitizePlugin,\n    SelectionPlugin,\n    SplitPlugin,\n    UserCommandPlugin,\n    StylePlugin,\n    ContentEditablePlugin,\n];\n\nexport const MAIN_PLUGINS = [\n    ...CORE_PLUGINS,\n    BannerPlugin,\n    ChatGPTTranslatePlugin,\n    ColorPlugin,\n    ColorUIPlugin,\n    SeparatorPlugin,\n    ColumnPlugin,\n    EmojiPlugin,\n    HintPlugin,\n    AlignPlugin,\n    ListPlugin,\n    MediaPlugin,\n    ImageSavePlugin,\n    ShortCutPlugin,\n    PowerboxPlugin,\n    SearchPowerboxPlugin,\n    MediaUrlPastePlugin,\n    StarPlugin,\n    TablePlugin,\n    TableAlignPlugin,\n    TableUIPlugin,\n    TabulationPlugin,\n    ToolbarPlugin,\n    FontPlugin, // note: if before ListPlugin, there are a few split tests that fails\n    FontFamilyPlugin,\n    IconPlugin,\n    IconColorPlugin,\n    ImagePlugin,\n    ImagePostProcessPlugin,\n    ImageCropPlugin,\n    DoubleClickImagePreviewPlugin,\n    LinkPlugin,\n    LinkPastePlugin,\n    FeffPlugin,\n    LinkSelectionPlugin,\n    OdooLinkSelectionPlugin,\n    PowerButtonsPlugin,\n    MoveNodePlugin,\n    LocalOverlayPlugin,\n    PositionPlugin,\n    TextDirectionPlugin,\n    InlineCodePlugin,\n    TableResizePlugin,\n    PlaceholderPlugin,\n    SelectionPlaceholderPlugin,\n];\n\nexport const COLLABORATION_PLUGINS = [\n    CollaborationPlugin,\n    CollaborationOdooPlugin,\n    CollaborationSelectionPlugin,\n    CollaborationSelectionAvatarPlugin,\n];\n\nexport const EMBEDDED_COMPONENT_PLUGINS = [\n    EmbeddedComponentPlugin,\n    TableOfContentPlugin,\n    ToggleBlockPlugin,\n    EmbeddedVideoPlugin,\n    EmbeddedYoutubePlugin,\n    CaptionPlugin,\n    EmbeddedFilePlugin,\n    SyntaxHighlightingPlugin,\n];\n\nexport const NO_EMBEDDED_COMPONENTS_FALLBACK_PLUGINS = [FilePlugin, VideoPlugin, YoutubePlugin];\n\nexport const EXTRA_PLUGINS = [\n    ...COLLABORATION_PLUGINS,\n    ...MAIN_PLUGINS,\n    ...EMBEDDED_COMPONENT_PLUGINS,\n    EditorVersionPlugin,\n    QWebPlugin,\n];\n", "import { Component, onMounted, onWillDestroy, useRef, useSubEnv } from \"@odoo/owl\";\nimport { Editor } from \"./editor\";\nimport { Toolbar } from \"./main/toolbar/toolbar\";\nimport { useChildRef, useSpellCheck } from \"@web/core/utils/hooks\";\nimport { LocalOverlayContainer } from \"./local_overlay_container\";\nimport { uniqueId } from \"@web/core/utils/functions\";\n\n/**\n * @typedef { import(\"./editor\").EditorConfig } EditorConfig\n **/\n\nfunction copyCssRules(sourceDoc, targetDoc) {\n    for (const sheet of sourceDoc.styleSheets) {\n        const rules = [];\n        for (const r of sheet.cssRules) {\n            rules.push(r.cssText);\n        }\n        const cssRules = rules.join(\" \");\n        const styleTag = targetDoc.createElement(\"style\");\n        styleTag.appendChild(targetDoc.createTextNode(cssRules));\n        targetDoc.head.appendChild(styleTag);\n    }\n}\n\nexport class Wysiwyg extends Component {\n    static template = \"html_editor.Wysiwyg\";\n    static components = { Toolbar, LocalOverlayContainer };\n    static props = {\n        config: { type: Object, optional: true },\n        class: { type: String, optional: true },\n        contentClass: { type: String, optional: true }, // on editable element\n        style: { type: String, optional: true },\n        iframe: { type: Boolean, optional: true },\n        copyCss: { type: Boolean, optional: true },\n        onLoad: { type: Function, optional: true },\n        onBlur: { type: Function, optional: true },\n        dynamicPlaceholder: { type: Boolean, optional: true },\n    };\n\n    static defaultProps = {\n        onLoad: () => {},\n        onBlur: () => {},\n    };\n\n    setup() {\n        this.overlayRef = useChildRef();\n        useSubEnv({\n            localOverlayContainerKey: uniqueId(\"wysiwyg\"),\n        });\n        const contentRef = useRef(\"content\");\n        this.editor = this.props.editor;\n        const config = this.getEditorConfig();\n        this.editor = new Editor(config, this.env.services);\n        this.props.onLoad(this.editor);\n        useSpellCheck({\n            refName: \"content\",\n        });\n\n        onMounted(() => {\n            /** @type { any } **/\n            const el = contentRef.el;\n\n            if (el.tagName === \"IFRAME\") {\n                // grab the inner body instead\n                const attachEditor = () => {\n                    if (!this.editor.isDestroyed) {\n                        if (this.props.copyCss) {\n                            copyCssRules(document, el.contentDocument);\n                        }\n                        const additionalClasses = el.dataset.class?.trim().split(\" \");\n                        if (additionalClasses) {\n                            for (const c of additionalClasses) {\n                                el.contentDocument.body.classList.add(c);\n                            }\n                        }\n                        this.editor.attachTo(el.contentDocument.body);\n                    }\n                };\n                if (el.contentDocument.readyState === \"complete\") {\n                    attachEditor();\n                } else {\n                    // in firefox, iframe is not immediately available. we need to wait\n                    // for it to be ready before mounting editor\n                    el.addEventListener(\n                        \"load\",\n                        () => {\n                            attachEditor();\n                            this.render();\n                        },\n                        { once: true }\n                    );\n                }\n            } else {\n                this.editor.attachTo(el);\n            }\n        });\n        onWillDestroy(() => this.editor.destroy(true));\n    }\n\n    getEditorConfig() {\n        return {\n            ...this.props.config,\n            localOverlayContainers: {\n                key: this.env.localOverlayContainerKey,\n                ref: this.overlayRef,\n            },\n        };\n    }\n}\n", "import { Dialog } from \"@web/core/dialog/dialog\";\nimport { Notebook } from \"@web/core/notebook/notebook\";\nimport { formatDateTime } from \"@web/core/l10n/dates\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { Component, onMounted, useState, markup, onWillStart, onWillDestroy } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { user } from \"@web/core/user\";\nimport { HtmlViewer } from \"@html_editor/components/html_viewer/html_viewer\";\nimport { READONLY_MAIN_EMBEDDINGS } from \"@html_editor/others/embedded_components/embedding_sets\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { loadBundle } from \"@web/core/assets\";\nimport { htmlReplaceAll } from \"@web/core/utils/html\";\n\nconst { DateTime } = luxon;\n\nexport class HistoryDialog extends Component {\n    static template = \"html_editor.HistoryDialog\";\n    static components = { Dialog, HtmlViewer, Notebook };\n    static props = {\n        recordId: Number,\n        recordModel: String,\n        close: Function,\n        restoreRequested: Function,\n        historyMetadata: Array,\n        versionedFieldName: String,\n        title: { String, optional: true },\n        noContentHelper: { String, optional: true }, //Markup\n        embeddedComponents: { Array, optional: true },\n    };\n\n    DEFAULT_AVATAR = \"/mail/static/src/img/smiley/avatar.jpg\";\n\n    static defaultProps = {\n        title: _t(\"History\"),\n        noContentHelper: markup(\"\"),\n        embeddedComponents: [...READONLY_MAIN_EMBEDDINGS],\n    };\n\n    state = useState({\n        revisionsData: [],\n        currentView: \"content\", // \"content\" or \"comparison\"\n        isComparisonSplit: false, // true for side-by-side, false for unified diff\n        revisionContent: null,\n        revisionComparison: null,\n        revisionId: null,\n        revisionLoading: true,\n        cssMaxHeight: 400,\n    });\n\n    setup() {\n        this.size = \"fullscreen\";\n        this.title = this.props.title;\n        this.orm = useService(\"orm\");\n        this.resizeObserver = null;\n\n        onWillStart(async () => {\n            // We include the current document version as the first revision,\n            // and we shift the rest of the metadata to be more logical for the user.\n            let revisionId = -1;\n            const revisionData = [];\n            for (const metadata of this.props.historyMetadata) {\n                revisionData.push({ ...metadata, revision_id: revisionId });\n                revisionId = metadata[\"revision_id\"];\n            }\n            // add the initial revision data based on the record creation date and user\n            const record = await this.orm.read(\n                this.props.recordModel,\n                [this.props.recordId],\n                [\"create_date\", \"create_uid\"]\n            );\n            revisionData.push({\n                revision_id: revisionId,\n                create_date: DateTime.fromFormat(\n                    record[0][\"create_date\"],\n                    \"yyyy-MM-dd HH:mm:ss\"\n                ).toISO(),\n                create_uid: record[0][\"create_uid\"][0],\n                create_user_name: record[0][\"create_uid\"][1],\n            });\n\n            this.state.revisionsData = revisionData;\n            this.resizeObserver = new ResizeObserver(this.resize.bind(this));\n            this.resizeObserver.observe(document.body);\n        });\n        onMounted(() => this.init());\n        onWillDestroy(() => {\n            this.resizeObserver?.disconnect();\n        });\n    }\n\n    resize() {\n        const dialogContainer = document.querySelector(\".html-history-dialog-container\");\n        const computedStyle = getComputedStyle(dialogContainer);\n        this.state.cssMaxHeight = parseInt(computedStyle.height.replace(\"px\", \"\")) - 160;\n    }\n\n    getConfig(value) {\n        return {\n            value: this.state[value],\n            embeddedComponents: this.props.embeddedComponents,\n        };\n    }\n\n    async init() {\n        // Load diff2html only in debug mode, as the side-by-side comparison is only available in debug mode.\n        if (this.env.debug) {\n            await loadBundle(\"html_editor.assets_history_diff\");\n        }\n        await this.updateCurrentRevision(this.state.revisionsData[0][\"revision_id\"]);\n        this.resize();\n    }\n\n    async updateCurrentRevision(revisionId) {\n        if (this.state.revisionId === revisionId) {\n            return;\n        }\n        this.state.revisionLoading = true;\n        this.state.revisionId = revisionId;\n        this.state.revisionContent = await this.getRevisionContent(revisionId);\n        this.state.revisionComparison = await this.getRevisionComparison(revisionId);\n        this.state.revisionComparisonSplit = await this.getRevisionComparisonSplit(revisionId);\n        this.state.revisionLoading = false;\n    }\n\n    getRevisionComparison = memoize(\n        async function getRevisionComparison(revisionId) {\n            if (revisionId === -1) {\n                return \"\";\n            }\n            const comparison = await this.orm.call(\n                this.props.recordModel,\n                \"html_field_history_get_comparison_at_revision\",\n                [this.props.recordId, this.props.versionedFieldName, revisionId]\n            );\n            return this._removeExternalBlockHtml(markup(comparison));\n        }.bind(this)\n    );\n\n    getRevisionComparisonSplit = memoize(\n        async function getRevisionComparisonSplit(revisionId) {\n            if (!this.env.debug || revisionId === -1) {\n                return \"\";\n            }\n            let unifiedDiffString = await this.orm.call(\n                this.props.recordModel,\n                \"html_field_history_get_unified_diff_at_revision\",\n                [this.props.recordId, this.props.versionedFieldName, revisionId]\n            );\n            // Remove unnecessary linebreaks\n            unifiedDiffString = unifiedDiffString.replace(/^\\s*[\\r\\n]/gm, \"\");\n            const colorScheme = cookie.get(\"color_scheme\") === \"dark\" ? \"dark\" : \"light\";\n            // eslint-disable-next-line no-undef\n            const diffHtml = Diff2Html.html(unifiedDiffString, {\n                drawFileList: false,\n                matching: \"lines\",\n                outputFormat: \"side-by-side\",\n                colorScheme: colorScheme,\n            });\n            return markup(diffHtml);\n        }.bind(this)\n    );\n\n    getRevisionContent = memoize(\n        async function getRevisionContent(revisionId) {\n            if (revisionId === -1) {\n                const curentContent = await this.orm.read(\n                    this.props.recordModel,\n                    [this.props.recordId],\n                    [this.props.versionedFieldName]\n                );\n                if (!curentContent || !curentContent.length) {\n                    return this.props.noContentHelper;\n                }\n                return this._removeExternalBlockHtml(\n                    markup(curentContent[0][this.props.versionedFieldName])\n                );\n            }\n            const content = await this.orm.call(\n                this.props.recordModel,\n                \"html_field_history_get_content_at_revision\",\n                [this.props.recordId, this.props.versionedFieldName, revisionId]\n            );\n            return this._removeExternalBlockHtml(markup(content));\n        }.bind(this)\n    );\n\n    async _onRestoreRevisionClick() {\n        this.env.services.ui.block();\n        const restoredContent = await this.getRevisionContent(this.state.revisionId);\n        this.props.restoreRequested(restoredContent, this.props.close);\n        this.env.services.ui.unblock();\n    }\n\n    _removeExternalBlockHtml(baseHtml) {\n        const filteringRegex = /<[a-z ]+data-embedded=\"(?:(?!<).)+<\\/[a-z]+>/gim;\n        const placeholderHtml = markup`<div class=\"embedded-history-dialog-placeholder\">${_t(\n            \"Dynamic element\"\n        )}</div>`;\n        return htmlReplaceAll(baseHtml, filteringRegex, () => placeholderHtml);\n    }\n\n    /**\n     * Getters\n     **/\n    getRevisionDate(revision) {\n        if (!revision || !revision[\"create_date\"]) {\n            return \"--\";\n        }\n        const userTZ = user.tz || \"local\";\n        return formatDateTime(\n            DateTime.fromISO(revision[\"create_date\"], { zone: \"utc\" }).setZone(userTZ),\n            { showSeconds: false }\n        );\n    }\n    getRevisionClasses(revision) {\n        let classesStr = \"btn\";\n\n        if (\n            this.state.revisionId !== -1 &&\n            (this.state.revisionId < revision.revision_id || revision.revision_id === -1)\n        ) {\n            classesStr += \" targeted\";\n        } else if (this.state.revisionId === revision.revision_id) {\n            classesStr += \" selected\";\n        }\n\n        return classesStr;\n    }\n    getRevisionAuthorAvatar(revision) {\n        if (!revision || !revision[\"create_uid\"]) {\n            return this.DEFAULT_AVATAR;\n        }\n        return `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${revision[\"create_uid\"]}`;\n    }\n\n    get currentRevision() {\n        const id = this.state?.revisionId || this.state.revisionsData[0][\"revision_id\"];\n        return this.state.revisionsData.find((revision) => revision[\"revision_id\"] === id);\n    }\n}\n", "import {\n    containsAnyNonPhrasingContent,\n    getDeepestPosition,\n    isContentEditable,\n    isElement,\n    isEmpty,\n    isMediaElement,\n    isProtected,\n    isProtecting,\n} from \"@html_editor/utils/dom_info\";\nimport { Plugin } from \"../plugin\";\nimport { fillEmpty } from \"@html_editor/utils/dom\";\nimport {\n    BASE_CONTAINER_CLASS,\n    baseContainerGlobalSelector,\n    createBaseContainer,\n} from \"../utils/base_container\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { childNodeIndex } from \"@html_editor/utils/position\";\n\n/**\n * @typedef { Object } BaseContainerShared\n * @property { BaseContainerPlugin['createBaseContainer'] } createBaseContainer\n * @property { BaseContainerPlugin['getDefaultNodeName'] } getDefaultNodeName\n * @property { BaseContainerPlugin['isCandidateForBaseContainer'] } isCandidateForBaseContainer\n */\n\n/**\n * @typedef {((node: Node) => boolean)[]} invalid_for_base_container_predicates\n */\n\nexport class BaseContainerPlugin extends Plugin {\n    static id = \"baseContainer\";\n    static shared = [\"createBaseContainer\", \"getDefaultNodeName\", \"isCandidateForBaseContainer\"];\n    static defaultConfig = {\n        baseContainers: [\"P\", \"DIV\"],\n    };\n    static dependencies = [\"selection\"];\n    /**\n     * Register one of the predicates for `invalid_for_base_container_predicates`\n     * as a property for optimization, see variants of `isCandidateForBaseContainer`.\n     */\n    hasNonPhrasingContentPredicate = (element) =>\n        element?.nodeType === Node.ELEMENT_NODE && containsAnyNonPhrasingContent(element);\n    /**\n     * The `unsplittable` predicate for `invalid_for_base_container_predicates`\n     * is defined in this file and not in split_plugin because it has to be removed\n     * in a specific case: see `isCandidateForBaseContainerAllowUnsplittable`.\n     */\n    isUnsplittablePredicate = (element) =>\n        this.getResource(\"unsplittable_node_predicates\").some((fn) => fn(element));\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        // `baseContainer` normalization should occur after every other normalization\n        // because a `div` may only have the baseContainer identity if it does not\n        // already have another incompatible identity given by another plugin.\n        normalize_handlers: withSequence(Infinity, this.normalizeDivBaseContainers.bind(this)),\n        delete_handlers: () => {\n            if (this.config.cleanEmptyStructuralContainers === false) {\n                return;\n            }\n            this.cleanEmptyStructuralContainers();\n        },\n        unsplittable_node_predicates: (node) => {\n            if (node.nodeName !== \"DIV\") {\n                return false;\n            }\n            return !this.isCandidateForBaseContainerAllowUnsplittable(node);\n        },\n        invalid_for_base_container_predicates: [\n            (node) =>\n                !node ||\n                node.nodeType !== Node.ELEMENT_NODE ||\n                !this.config.baseContainers.includes(node.tagName) ||\n                isProtected(node) ||\n                isProtecting(node) ||\n                isMediaElement(node),\n            this.isUnsplittablePredicate,\n            this.hasNonPhrasingContentPredicate,\n        ],\n        system_classes: [BASE_CONTAINER_CLASS],\n    };\n\n    createBaseContainer(nodeName = this.getDefaultNodeName()) {\n        return createBaseContainer(nodeName, this.document);\n    }\n\n    getDefaultNodeName() {\n        return this.config.baseContainers[0];\n    }\n\n    cleanEmptyStructuralContainers() {\n        const node = this.document.getSelection().anchorNode;\n\n        if (!isElement(node) || !isEmpty(node)) {\n            return;\n        }\n\n        const closestEditable = (n) =>\n            isContentEditable(n.parentElement) ? closestEditable(n.parentElement) : n;\n\n        const isUnsplittable = this.isUnsplittablePredicate(node);\n        const isCandidateForBase = this.isCandidateForBaseContainerAllowUnsplittable(node);\n\n        if (isUnsplittable || !isCandidateForBase) {\n            return;\n        }\n\n        let anchorNode = node.parentElement;\n        if (\n            anchorNode === closestEditable(node) ||\n            !this.config.baseContainers.includes(anchorNode.nodeName) ||\n            this.getResource(\"unremovable_node_predicates\").some((p) => p(anchorNode))\n        ) {\n            return;\n        }\n\n        if (isEmpty(anchorNode)) {\n            fillEmpty(anchorNode);\n        }\n\n        let anchorOffset = childNodeIndex(node);\n        node.remove();\n\n        [anchorNode, anchorOffset] = getDeepestPosition(anchorNode, anchorOffset);\n        this.dependencies.selection.setSelection({\n            anchorNode,\n            anchorOffset,\n        });\n    }\n\n    /**\n     * Evaluate if an element is eligible to become a baseContainer (i.e. an\n     * unmarked div which could receive baseContainer attributes to inherit\n     * paragraph-like features).\n     *\n     * This function considers unsplittable and childNodes.\n     */\n    isCandidateForBaseContainer(element) {\n        return !this.getResource(\"invalid_for_base_container_predicates\").some((fn) => fn(element));\n    }\n\n    /**\n     * Evaluate if an element would be eligible to become a baseContainer\n     * without considering unsplittable.\n     *\n     * This function is only meant to be used during `unsplittable_node_predicates` to\n     * avoid an infinite loop:\n     * Considering a `DIV`,\n     * - During `unsplittable_node_predicates`, one predicate should return true\n     *   if the `DIV` is NOT a baseContainer candidate (Odoo specification),\n     *   therefore `invalid_for_base_container_predicates` should be evaluated.\n     * - During `invalid_for_base_container_predicates`, one predicate should\n     *   return true if the `DIV` is unsplittable, because a node has to be\n     *   splittable to use the featureSet associated with paragraphs.\n     * Each resource has to call the other. To avoid the issue, during\n     * `unsplittable_node_predicates`, the baseContainer predicate will execute\n     * all predicates for `invalid_for_base_container_predicates` except\n     * the one using `unsplittable_node_predicates`, since it is already being\n     * evaluated.\n     *\n     * In simpler terms:\n     * A `DIV` is unsplittable by default;\n     * UNLESS it is eligible to be a baseContainer => it becomes one;\n     * UNLESS it has to be unsplittable for an explicit reason (i.e. has class\n     * oe_unbreakable) => it stays unsplittable.\n     */\n    isCandidateForBaseContainerAllowUnsplittable(element) {\n        for (const predicate of this.getResource(\"invalid_for_base_container_predicates\")) {\n            if (predicate === this.isUnsplittablePredicate) {\n                continue;\n            }\n            if (predicate(element)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Evaluate if an element would be eligible to become a baseContainer\n     * without considering its childNodes.\n     *\n     * This function is only meant to be used internally, to avoid having to\n     * compute childNodes multiple times in more complex operations.\n     */\n    shallowIsCandidateForBaseContainer(element) {\n        const predicates = this.getResource(\"invalid_for_base_container_predicates\");\n        for (const predicate of predicates) {\n            if (predicate === this.hasNonPhrasingContentPredicate) {\n                continue;\n            }\n            if (predicate(element)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    cleanForSave({ root }) {\n        for (const baseContainer of selectElements(root, `.${BASE_CONTAINER_CLASS}`)) {\n            baseContainer.classList.remove(BASE_CONTAINER_CLASS);\n            if (baseContainer.classList.length === 0) {\n                baseContainer.removeAttribute(\"class\");\n            }\n        }\n    }\n\n    normalizeDivBaseContainers(element = this.editable) {\n        if (this.config.baseContainers && !this.config.baseContainers.includes(\"DIV\")) {\n            return;\n        }\n        const newBaseContainers = [];\n        const divSelector = `div:not(.${BASE_CONTAINER_CLASS})`;\n        const targets = [...element.querySelectorAll(divSelector)];\n        if (element.matches(divSelector)) {\n            targets.unshift(element);\n        }\n        for (const div of targets) {\n            if (\n                // Ensure that newly created `div` baseContainers are never themselves\n                // children of a baseContainer. BaseContainers should always only\n                // contain phrasing content (even `div`), because they could be\n                // converted to an element which can actually only contain phrasing\n                // content. In practice a div should never be a child of a\n                // baseContainer, since a baseContainer should only contain\n                // phrasingContent.\n                !div.parentElement?.matches(baseContainerGlobalSelector) &&\n                this.shallowIsCandidateForBaseContainer(div) &&\n                !containsAnyNonPhrasingContent(div)\n            ) {\n                div.classList.add(BASE_CONTAINER_CLASS);\n                newBaseContainers.push(div);\n                fillEmpty(div);\n            }\n        }\n    }\n}\n", "import { isTextNode, isParagraphRelatedElement, isEmptyBlock } from \"../utils/dom_info\";\nimport { Plugin } from \"../plugin\";\nimport { closestBlock } from \"../utils/blocks\";\nimport { unwrapContents, wrapInlinesInBlocks, splitTextNode, fillEmpty } from \"../utils/dom\";\nimport { fillClipboardData } from \"../utils/clipboard\";\nimport { childNodes, closestElement } from \"../utils/dom_traversal\";\nimport { parseHTML } from \"../utils/html\";\nimport {\n    baseContainerGlobalSelector,\n    getBaseContainerSelector,\n} from \"@html_editor/utils/base_container\";\nimport { DIRECTIONS } from \"../utils/position\";\nimport { isHtmlContentSupported } from \"./selection_plugin\";\n\n/**\n * @typedef { import(\"./selection_plugin\").EditorSelection } EditorSelection\n *\n * @typedef {(() => boolean)[]} bypass_paste_image_files\n */\n\nconst CLIPBOARD_BLACKLISTS = {\n    unwrap: [\n        // These elements' children will be unwrapped.\n        \".Apple-interchange-newline\",\n        \"DIV\", // DIV is unwrapped unless eligible to be a baseContainer, see cleanForPaste\n    ],\n    remove: [\"META\", \"STYLE\", \"SCRIPT\"], // These elements will be removed along with their children.\n};\nexport const CLIPBOARD_WHITELISTS = {\n    nodes: [\n        // Style\n        \"P\",\n        \"H1\",\n        \"H2\",\n        \"H3\",\n        \"H4\",\n        \"H5\",\n        \"H6\",\n        \"BLOCKQUOTE\",\n        \"PRE\",\n        // List\n        \"UL\",\n        \"OL\",\n        \"LI\",\n        // Inline style\n        \"I\",\n        \"B\",\n        \"U\",\n        \"S\",\n        \"EM\",\n        \"FONT\",\n        \"STRONG\",\n        // Table\n        \"TABLE\",\n        \"THEAD\",\n        \"TH\",\n        \"TBODY\",\n        \"TR\",\n        \"TD\",\n        // Miscellaneous\n        \"IMG\",\n        \"BR\",\n        \"A\",\n        \".fa\",\n    ],\n    classes: [\n        // Media\n        /^float-/,\n        \"d-block\",\n        \"mx-auto\",\n        \"img-fluid\",\n        \"img-thumbnail\",\n        \"rounded\",\n        \"rounded-circle\",\n        // Odoo tables\n        \"o_table\",\n        \"table\",\n        \"table-bordered\",\n        /^padding-/,\n        /^shadow/,\n        // Odoo colors\n        /^text-o-/,\n        /^bg-o-/,\n        // Odoo lists\n        \"o_checked\",\n        \"o_checklist\",\n        \"oe-nested\",\n        // Miscellaneous\n        /^btn/,\n        /^fa/,\n    ],\n    attributes: [\"class\", \"href\", \"src\", \"target\"],\n    styledTags: [\"SPAN\", \"B\", \"STRONG\", \"I\", \"S\", \"U\", \"FONT\", \"TD\"],\n};\n\nconst ONLY_LINK_REGEX = /^(https?:\\/\\/)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-./?%&=]*)?$/i;\n\n/**\n * @typedef {Object} ClipboardShared\n * @property {ClipboardPlugin['pasteText']} pasteText\n */\n\n/**\n * @typedef {((img: HTMLImageElement) => void)[]} added_image_handlers\n * @typedef {(() => void)[]} after_paste_handlers\n * @typedef {(() => void)[]} before_paste_handlers\n *\n * @typedef {((selection: EditorSelection, text: string) => boolean)[]} paste_text_overrides\n *\n * @typedef {((\n *     clonedContents: DocumentFragment,\n *     selection: EditorSelection\n *   ) => void | clonedContents)[]} clipboard_content_processors\n * @typedef {((textContent: string) => string)[]} clipboard_text_processors\n */\n\nexport class ClipboardPlugin extends Plugin {\n    static id = \"clipboard\";\n    static dependencies = [\n        \"baseContainer\",\n        \"dom\",\n        \"selection\",\n        \"sanitize\",\n        \"history\",\n        \"split\",\n        \"delete\",\n        \"lineBreak\",\n    ];\n    static shared = [\"pasteText\"];\n\n    setup() {\n        this.addDomListener(this.editable, \"copy\", this.onCopy);\n        this.addDomListener(this.editable, \"cut\", this.onCut);\n        this.addDomListener(this.editable, \"paste\", this.onPaste);\n        this.addDomListener(this.editable, \"dragstart\", this.onDragStart);\n        this.addDomListener(this.editable, \"drop\", this.onDrop);\n    }\n\n    onCut(ev) {\n        this.onCopy(ev);\n        this.dependencies.history.stageSelection();\n        this.dependencies.delete.deleteSelection();\n        this.dependencies.history.addStep();\n    }\n\n    /**\n     * @param {ClipboardEvent} ev\n     */\n    onCopy(ev) {\n        ev.preventDefault();\n        const selection = this.dependencies.selection.getEditableSelection();\n        let clonedContents = selection.cloneContents();\n        if (!clonedContents.hasChildNodes()) {\n            return;\n        }\n\n        // Prepare text content for clipboard.\n        let textContent = selection.textContent();\n        for (const processor of this.getResource(\"clipboard_text_processors\")) {\n            textContent = processor(textContent);\n        }\n        ev.clipboardData.setData(\"text/plain\", textContent);\n\n        // Prepare html content for clipboard.\n        for (const processor of this.getResource(\"clipboard_content_processors\")) {\n            clonedContents = processor(clonedContents, selection) || clonedContents;\n        }\n        this.dependencies.dom.removeSystemProperties(clonedContents);\n        fillClipboardData(ev, clonedContents);\n    }\n\n    /**\n     * Handle safe pasting of html or plain text into the editor.\n     */\n    onPaste(ev) {\n        let selection = this.dependencies.selection.getEditableSelection();\n        if (\n            !selection.anchorNode.isConnected ||\n            !closestElement(selection.anchorNode).isContentEditable\n        ) {\n            return;\n        }\n        ev.preventDefault();\n\n        this.dependencies.history.stageSelection();\n\n        this.dispatchTo(\"before_paste_handlers\", selection, ev);\n        // refresh selection after potential changes from `before_paste` handlers\n        selection = this.dependencies.selection.getEditableSelection();\n\n        this.handlePasteUnsupportedHtml(selection, ev.clipboardData) ||\n            this.handlePasteOdooEditorHtml(ev.clipboardData) ||\n            this.handlePasteHtml(selection, ev.clipboardData) ||\n            this.handlePasteText(selection, ev.clipboardData);\n\n        this.dispatchTo(\"after_paste_handlers\", selection);\n        this.dependencies.history.addStep();\n    }\n    /**\n     * @param {EditorSelection} selection\n     * @param {DataTransfer} clipboardData\n     */\n    handlePasteUnsupportedHtml(selection, clipboardData) {\n        if (!isHtmlContentSupported(selection)) {\n            const text = clipboardData.getData(\"text/plain\");\n            this.dependencies.dom.insert(text);\n            return true;\n        }\n    }\n    /**\n     * @param {DataTransfer} clipboardData\n     */\n    handlePasteOdooEditorHtml(clipboardData) {\n        const odooEditorHtml = clipboardData.getData(\"application/vnd.odoo.odoo-editor\");\n        const textContent = clipboardData.getData(\"text/plain\");\n        if (ONLY_LINK_REGEX.test(textContent)) {\n            return false;\n        }\n        if (odooEditorHtml) {\n            const fragment = parseHTML(this.document, odooEditorHtml);\n            this.dependencies.sanitize.sanitize(fragment);\n            if (fragment.hasChildNodes()) {\n                this.dependencies.dom.insert(fragment);\n            }\n            return true;\n        }\n    }\n    /**\n     * @param {EditorSelection} selection\n     * @param {DataTransfer} clipboardData\n     */\n    handlePasteHtml(selection, clipboardData) {\n        const files = this.delegateTo(\"bypass_paste_image_files\")\n            ? []\n            : getImageFiles(clipboardData);\n        const clipboardHtml = clipboardData.getData(\"text/html\");\n        const textContent = clipboardData.getData(\"text/plain\");\n        if (ONLY_LINK_REGEX.test(textContent)) {\n            return false;\n        }\n        if (files.length || clipboardHtml) {\n            const clipboardElem = this.prepareClipboardData(clipboardHtml);\n            // @phoenix @todo: should it be handled in table plugin?\n            // When copy pasting a table from the outside, a picture of the\n            // table can be included in the clipboard as an image file. In that\n            // particular case the html table is given a higher priority than\n            // the clipboard picture.\n            if (files.length && !clipboardElem.querySelector(\"table\")) {\n                // @phoenix @todo: should it be handled in image plugin?\n                return this.addImagesFiles(files).then((html) => {\n                    this.dependencies.dom.insert(html);\n                    this.dependencies.history.addStep();\n                });\n            } else if (clipboardElem.hasChildNodes()) {\n                if (closestElement(selection.anchorNode, \"a\")) {\n                    this.dependencies.dom.insert(clipboardElem.textContent);\n                } else {\n                    this.dependencies.dom.insert(clipboardElem);\n                }\n            }\n            return true;\n        }\n    }\n    /**\n     * @param {EditorSelection} selection\n     * @param {DataTransfer} clipboardData\n     */\n    handlePasteText(selection, clipboardData) {\n        const text = clipboardData.getData(\"text/plain\");\n        if (this.delegateTo(\"paste_text_overrides\", selection, text)) {\n            return;\n        } else {\n            this.pasteText(text);\n        }\n    }\n    /**\n     * @param {string} text\n     */\n    pasteText(text) {\n        const textFragments = text.split(/\\r?\\n/);\n        let selection = this.dependencies.selection.getEditableSelection();\n        const preEl = closestElement(selection.anchorNode, \"PRE\");\n        let textIndex = 1;\n        for (const textFragment of textFragments) {\n            let modifiedTextFragment = textFragment;\n\n            // <pre> preserves whitespace by default, so no need for &nbsp.\n            if (!preEl) {\n                // Replace consecutive spaces by alternating nbsp.\n                modifiedTextFragment = textFragment.replace(/( {2,})/g, (match) => {\n                    let alternateValue = false;\n                    return match.replace(/ /g, () => {\n                        alternateValue = !alternateValue;\n                        const replaceContent = alternateValue ? \"\\u00A0\" : \" \";\n                        return replaceContent;\n                    });\n                });\n            }\n            this.dependencies.dom.insert(modifiedTextFragment);\n            if (textIndex < textFragments.length) {\n                selection = this.dependencies.selection.getEditableSelection();\n                // Break line by inserting new paragraph and\n                // remove current paragraph's bottom margin.\n                const block = closestBlock(selection.anchorNode);\n                if (\n                    this.dependencies.split.isUnsplittable(block) ||\n                    closestElement(selection.anchorNode).tagName === \"PRE\"\n                ) {\n                    this.dependencies.lineBreak.insertLineBreak();\n                } else {\n                    const [blockBefore] = this.dependencies.split.splitBlock();\n                    if (\n                        block &&\n                        block.matches(baseContainerGlobalSelector) &&\n                        blockBefore &&\n                        !blockBefore.matches(getBaseContainerSelector(\"DIV\"))\n                    ) {\n                        // Do something only if blockBefore is not a DIV (which is the no-margin option)\n                        // replace blockBefore by a DIV.\n                        const div = this.dependencies.baseContainer.createBaseContainer(\"DIV\");\n                        const cursors = this.dependencies.selection.preserveSelection();\n                        blockBefore.before(div);\n                        div.replaceChildren(...childNodes(blockBefore));\n                        blockBefore.remove();\n                        cursors.remapNode(blockBefore, div).restore();\n                    }\n                }\n            }\n            textIndex++;\n        }\n    }\n\n    /**\n     * Prepare clipboard data (text/html) for safe pasting into the editor.\n     *\n     * @private\n     * @param {string} clipboardData\n     * @returns {DocumentFragment}\n     */\n    prepareClipboardData(clipboardData) {\n        const fragment = parseHTML(this.document, clipboardData);\n        this.dependencies.sanitize.sanitize(fragment);\n        const container = this.document.createElement(\"fake-container\");\n        container.append(fragment);\n\n        for (const tableElement of container.querySelectorAll(\"table\")) {\n            tableElement.classList.add(\"table\", \"table-bordered\", \"o_table\");\n        }\n        if (this.delegateTo(\"bypass_paste_image_files\")) {\n            for (const imgElement of container.querySelectorAll(\"img\")) {\n                imgElement.remove();\n            }\n        }\n\n        // todo: should it be in its own plugin ?\n        const progId = container.querySelector('meta[name=\"ProgId\"]');\n        if (progId && progId.content === \"Excel.Sheet\") {\n            // Microsoft Excel keeps table style in a <style> tag with custom\n            // classes. The following lines parse that style and apply it to the\n            // style attribute of <td> tags with matching classes.\n            const xlStylesheet = container.querySelector(\"style\");\n            const xlNodes = container.querySelectorAll(\"[class*=xl],[class*=font]\");\n            for (const xlNode of xlNodes) {\n                for (const xlClass of xlNode.classList) {\n                    // Regex captures a CSS rule definition for that xlClass.\n                    const xlStyle = xlStylesheet.textContent\n                        .match(`.${xlClass}[^{]*{(?<xlStyle>[^}]*)}`)\n                        .groups.xlStyle.replace(\"background:\", \"background-color:\");\n                    xlNode.setAttribute(\"style\", xlNode.style.cssText + \";\" + xlStyle);\n                }\n            }\n        }\n        const childContent = childNodes(container);\n        for (const child of childContent) {\n            this.cleanForPaste(child);\n        }\n        // Identify the closest baseContainer from the selection. This will\n        // determine which baseContainer will be used by default for the\n        // clipboard content if it has to be modified.\n        const selection = this.dependencies.selection.getEditableSelection();\n        const closestBaseContainer =\n            selection.anchorNode &&\n            closestElement(selection.anchorNode, baseContainerGlobalSelector);\n        // Force inline nodes at the root of the container into separate `baseContainers`\n        // elements. This is a tradeoff to ensure some features that rely on\n        // nodes having a parent (e.g. convert to list, title, etc.) can work\n        // properly on such nodes without having to actually handle that\n        // particular case in all of those functions. In fact, this case cannot\n        // happen on a new document created using this editor, but will happen\n        // instantly when editing a document that was created from Etherpad.\n        wrapInlinesInBlocks(container, {\n            baseContainerNodeName:\n                closestBaseContainer?.nodeName ||\n                this.dependencies.baseContainer.getDefaultNodeName(),\n        });\n        const result = this.document.createDocumentFragment();\n        result.replaceChildren(...childNodes(container));\n\n        // Split elements containing <br> into separate elements for each line.\n        const brs = result.querySelectorAll(\"br\");\n        for (const br of brs) {\n            const block = closestBlock(br);\n            if (\n                (isParagraphRelatedElement(block) ||\n                    this.dependencies.baseContainer.isCandidateForBaseContainer(block)) &&\n                block.nodeName !== \"PRE\"\n            ) {\n                // A linebreak at the beginning of a block is an empty line.\n                const isEmptyLine = block.firstChild.nodeName === \"BR\";\n                // Split blocks around it until only the BR remains in the\n                // block.\n                const remainingBrContainer = this.dependencies.split.splitAroundUntil(br, block);\n                // Remove the container unless it represented an empty line.\n                if (!isEmptyLine) {\n                    remainingBrContainer.remove();\n                }\n            }\n        }\n        return result;\n    }\n    /**\n     * Clean a node for safely pasting. Cleaning an element involves unwrapping\n     * its contents if it's an illegal (blacklisted or not whitelisted) element,\n     * or removing its illegal attributes and classes.\n     *\n     * @param {Node} node\n     */\n    cleanForPaste(node) {\n        if (\n            !this.isWhitelisted(node) ||\n            this.isBlacklisted(node) ||\n            // Google Docs have their html inside a B tag with custom id.\n            (node.id && node.id.startsWith(\"docs-internal-guid\"))\n        ) {\n            if (!node.matches || node.matches(CLIPBOARD_BLACKLISTS.remove.join(\",\"))) {\n                node.remove();\n            } else {\n                let childrenNodes;\n                if (node.nodeName === \"DIV\") {\n                    if (!node.hasChildNodes()) {\n                        node.remove();\n                        return;\n                    } else if (this.dependencies.baseContainer.isCandidateForBaseContainer(node)) {\n                        const whiteSpace = node.style?.whiteSpace;\n                        if (whiteSpace && ![\"normal\", \"nowrap\"].includes(whiteSpace)) {\n                            node.innerHTML = node.innerHTML.replace(/\\n/g, \"<br>\");\n                        }\n                        const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                        const dir = node.getAttribute(\"dir\");\n                        if (dir) {\n                            baseContainer.setAttribute(\"dir\", dir);\n                        }\n                        baseContainer.append(...node.childNodes);\n\n                        node.replaceWith(baseContainer);\n                        childrenNodes = childNodes(baseContainer);\n                    } else {\n                        childrenNodes = unwrapContents(node);\n                    }\n                } else {\n                    // Unwrap the illegal node's contents.\n                    childrenNodes = unwrapContents(node);\n                }\n                for (const child of childrenNodes) {\n                    this.cleanForPaste(child);\n                }\n            }\n        } else if (node.nodeType !== Node.TEXT_NODE) {\n            if (node.nodeName === \"THEAD\") {\n                const tbody = node.nextElementSibling;\n                if (tbody) {\n                    // If a <tbody> already exists, move all rows from\n                    // <thead> into the start of <tbody>.\n                    tbody.prepend(...node.children);\n                    node.remove();\n                    node = tbody;\n                } else {\n                    // Otherwise, replace the <thead> with <tbody>\n                    node = this.dependencies.dom.setTagName(node, \"TBODY\");\n                }\n            } else if ([\"TD\", \"TH\"].includes(node.nodeName)) {\n                // Insert base container into empty TD.\n                if (isEmptyBlock(node)) {\n                    const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                    fillEmpty(baseContainer);\n                    node.replaceChildren(baseContainer);\n                }\n\n                if (node.hasAttribute(\"bgcolor\") && !node.style[\"background-color\"]) {\n                    node.style[\"background-color\"] = node.getAttribute(\"bgcolor\");\n                }\n            } else if (node.nodeName === \"FONT\") {\n                // FONT tags have some style information in custom attributes,\n                // this maps them to the style attribute.\n                if (node.hasAttribute(\"color\") && !node.style[\"color\"]) {\n                    node.style[\"color\"] = node.getAttribute(\"color\");\n                }\n                if (node.hasAttribute(\"size\") && !node.style[\"font-size\"]) {\n                    // FONT size uses non-standard numeric values.\n                    node.style[\"font-size\"] = +node.getAttribute(\"size\") + 10 + \"pt\";\n                }\n            } else if (\n                [\"S\", \"U\"].includes(node.nodeName) &&\n                childNodes(node).length === 1 &&\n                node.firstChild.nodeName === \"FONT\"\n            ) {\n                // S and U tags sometimes contain FONT tags. We prefer the\n                // strike to adopt the style of the text, so we invert them.\n                const fontNode = node.firstChild;\n                node.before(fontNode);\n                node.replaceChildren(...childNodes(fontNode));\n                fontNode.appendChild(node);\n            } else if (\n                node.nodeName === \"IMG\" &&\n                node.getAttribute(\"aria-roledescription\") === \"checkbox\"\n            ) {\n                const checklist = node.closest(\"ul\");\n                const closestLi = node.closest(\"li\");\n                if (checklist) {\n                    checklist.classList.add(\"o_checklist\");\n                    if (node.getAttribute(\"alt\") === \"checked\") {\n                        closestLi.classList.add(\"o_checked\");\n                    }\n                    node.remove();\n                    node = checklist;\n                }\n            }\n            // Remove all illegal attributes and classes from the node, then\n            // clean its children.\n            for (const attribute of [...node.attributes]) {\n                // Keep allowed styles on nodes with allowed tags.\n                // todo: should the whitelist be a resource?\n                if (\n                    CLIPBOARD_WHITELISTS.styledTags.includes(node.nodeName) &&\n                    attribute.name === \"style\"\n                ) {\n                    node.removeAttribute(attribute.name);\n                    if ([\"SPAN\", \"FONT\"].includes(node.tagName)) {\n                        for (const unwrappedNode of unwrapContents(node)) {\n                            this.cleanForPaste(unwrappedNode);\n                        }\n                    }\n                } else if (!this.isWhitelisted(attribute)) {\n                    node.removeAttribute(attribute.name);\n                }\n            }\n            for (const klass of [...node.classList]) {\n                if (!this.isWhitelisted(klass)) {\n                    node.classList.remove(klass);\n                }\n            }\n            for (const child of childNodes(node)) {\n                this.cleanForPaste(child);\n            }\n        }\n    }\n    /**\n     * Return true if the given attribute, class or node is whitelisted for\n     * pasting, false otherwise.\n     *\n     * @private\n     * @param {Attr | string | Node} item\n     * @returns {boolean}\n     */\n    isWhitelisted(item) {\n        if (item.nodeType === Node.ATTRIBUTE_NODE) {\n            return CLIPBOARD_WHITELISTS.attributes.includes(item.name);\n        } else if (typeof item === \"string\") {\n            return CLIPBOARD_WHITELISTS.classes.some((okClass) =>\n                okClass instanceof RegExp ? okClass.test(item) : okClass === item\n            );\n        } else {\n            return isTextNode(item) || item.matches?.(CLIPBOARD_WHITELISTS.nodes.join(\",\"));\n        }\n    }\n    /**\n     * Return true if the given node is blacklisted for pasting, false\n     * otherwise.\n     *\n     * @private\n     * @param {Node} node\n     * @returns {boolean}\n     */\n    isBlacklisted(node) {\n        return (\n            !isTextNode(node) &&\n            node.matches([].concat(...Object.values(CLIPBOARD_BLACKLISTS)).join(\",\"))\n        );\n    }\n\n    /**\n     * @param {DragEvent} ev\n     */\n    onDragStart(ev) {\n        if (ev.target.nodeName === \"IMG\") {\n            this.dragImage = ev.target instanceof HTMLElement && ev.target;\n            ev.dataTransfer.setData(\n                \"application/vnd.odoo.odoo-editor-node\",\n                this.dragImage.outerHTML\n            );\n        }\n    }\n    /**\n     * Handle safe dropping of html into the editor.\n     *\n     * @param {DragEvent} ev\n     */\n    async onDrop(ev) {\n        ev.preventDefault();\n        const selection = this.dependencies.selection.getEditableSelection();\n        if (!isHtmlContentSupported(selection)) {\n            return;\n        }\n        const nodeToSplit =\n            selection.direction === DIRECTIONS.RIGHT ? selection.focusNode : selection.anchorNode;\n        const offsetToSplit =\n            selection.direction === DIRECTIONS.RIGHT\n                ? selection.focusOffset\n                : selection.anchorOffset;\n        if (nodeToSplit.nodeType === Node.TEXT_NODE && !selection.isCollapsed) {\n            const selectionToRestore = this.dependencies.selection.preserveSelection();\n            // Split the text node beforehand to ensure the insertion offset\n            // remains correct after deleting the selection.\n            splitTextNode(nodeToSplit, offsetToSplit, DIRECTIONS.LEFT);\n            selectionToRestore.restore();\n        }\n\n        const dataTransfer = (ev.originalEvent || ev).dataTransfer;\n        const imageNodeHTML = ev.dataTransfer.getData(\"application/vnd.odoo.odoo-editor-node\");\n        const image =\n            imageNodeHTML &&\n            this.dragImage &&\n            imageNodeHTML === this.dragImage.outerHTML &&\n            this.dragImage;\n\n        const fileTransferItems = getImageFiles(dataTransfer);\n        const htmlTransferItem = [...dataTransfer.items].find((item) => item.type === \"text/html\");\n        if (image || fileTransferItems.length || htmlTransferItem) {\n            if (this.document.caretPositionFromPoint) {\n                const range = this.document.caretPositionFromPoint(ev.clientX, ev.clientY);\n                this.dependencies.delete.deleteSelection();\n                this.dependencies.selection.setSelection({\n                    anchorNode: range.offsetNode,\n                    anchorOffset: range.offset,\n                });\n            } else if (this.document.caretRangeFromPoint) {\n                const range = this.document.caretRangeFromPoint(ev.clientX, ev.clientY);\n                this.dependencies.delete.deleteSelection();\n                this.dependencies.selection.setSelection({\n                    anchorNode: range.startContainer,\n                    anchorOffset: range.startOffset,\n                });\n            }\n        }\n        if (image) {\n            const fragment = this.document.createDocumentFragment();\n            fragment.append(image);\n            this.dependencies.dom.insert(fragment);\n            this.dependencies.history.addStep();\n        } else if (fileTransferItems.length) {\n            const html = await this.addImagesFiles(fileTransferItems);\n            this.dependencies.dom.insert(html);\n            this.dependencies.history.addStep();\n        } else if (htmlTransferItem) {\n            htmlTransferItem.getAsString((pastedText) => {\n                this.dependencies.dom.insert(this.prepareClipboardData(pastedText));\n                this.dependencies.history.addStep();\n            });\n        }\n    }\n    // @phoenix @todo: move to image or image paste plugin?\n    /**\n     * Add images inside the editable at the current selection.\n     *\n     * @param {File[]} imageFiles\n     */\n    async addImagesFiles(imageFiles) {\n        const promises = [];\n        for (const imageFile of imageFiles) {\n            const imageNode = this.document.createElement(\"img\");\n            imageNode.classList.add(\"img-fluid\");\n            this.dispatchTo(\"added_image_handlers\", imageNode);\n            imageNode.dataset.fileName = imageFile.name;\n            promises.push(\n                getImageUrl(imageFile).then((url) => {\n                    imageNode.src = url;\n                    return imageNode;\n                })\n            );\n        }\n        const nodes = await Promise.all(promises);\n        const fragment = this.document.createDocumentFragment();\n        fragment.append(...nodes);\n        return fragment;\n    }\n}\n\n/**\n * @param {DataTransfer} dataTransfer\n */\nfunction getImageFiles(dataTransfer) {\n    return [...dataTransfer.items]\n        .filter((item) => item.kind === \"file\" && item.type.includes(\"image/\"))\n        .map((item) => item.getAsFile());\n}\n/**\n * @param {File} file\n */\nfunction getImageUrl(file) {\n    return new Promise((resolve, reject) => {\n        const reader = new FileReader();\n\n        reader.readAsDataURL(file);\n        reader.onloadend = (e) => {\n            if (reader.error) {\n                return reject(reader.error);\n            }\n            resolve(e.target.result);\n        };\n    });\n}\n", "import { isProtected } from \"@html_editor/utils/dom_info\";\nimport { Plugin } from \"../plugin\";\nimport { descendants } from \"../utils/dom_traversal\";\n\nexport class CommentPlugin extends Plugin {\n    static id = \"comment\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        normalize_handlers: this.removeComment.bind(this),\n    };\n\n    removeComment(node) {\n        for (const el of [node, ...descendants(node)]) {\n            if (el.nodeType === Node.COMMENT_NODE && !isProtected(el)) {\n                el.remove();\n            }\n        }\n    }\n}\n", "import { isArtificialVoidElement } from \"@html_editor/core/selection_plugin\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { withSequence } from \"@html_editor/utils/resource\";\n\n/** @typedef {import(\"@html_editor/editor\").EditorContext} EditorContext */\n/** @typedef {import(\"plugins\").CSSSelector} CSSSelector */\n\n/**\n * @typedef {((el: HTMLElement) => boolean)[]} valid_contenteditable_predicates\n *\n * @typedef {((root: EditorContext[\"editable\"]) => HTMLElement[])[]} content_editable_providers\n * @typedef {((root: EditorContext[\"editable\"]) => HTMLElement[])[]} content_not_editable_providers\n *\n * @typedef {CSSSelector[]} contenteditable_to_remove_selector\n */\n\n/**\n * This plugin is responsible for setting the contenteditable attribute on some\n * elements.\n *\n * The content_editable_providers and content_not_editable_providers resources\n * allow other plugins to easily add editable or non editable elements.\n */\n\nexport class ContentEditablePlugin extends Plugin {\n    static id = \"contentEditablePlugin\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        normalize_handlers: withSequence(5, this.normalize.bind(this)),\n        clean_for_save_handlers: withSequence(Infinity, this.cleanForSave.bind(this)),\n    };\n\n    normalize(root) {\n        const contentNotEditableEls = [];\n        for (const fn of this.getResource(\"content_not_editable_providers\")) {\n            contentNotEditableEls.push(...fn(root));\n        }\n        for (const contentNotEditableEl of contentNotEditableEls) {\n            contentNotEditableEl.setAttribute(\"contenteditable\", \"false\");\n        }\n        const contentEditableEls = [];\n        for (const fn of this.getResource(\"content_editable_providers\")) {\n            contentEditableEls.push(...fn(root));\n        }\n        const filteredContentEditableEls = contentEditableEls.filter((contentEditableEl) =>\n            this.getResource(\"valid_contenteditable_predicates\").every((p) => p(contentEditableEl))\n        );\n        for (const contentEditableEl of filteredContentEditableEls) {\n            if (!contentEditableEl.isContentEditable) {\n                if (\n                    isArtificialVoidElement(contentEditableEl) ||\n                    contentEditableEl.nodeName === \"IMG\"\n                ) {\n                    contentEditableEl.classList.add(\"o_editable_media\");\n                    continue;\n                }\n                if (!contentNotEditableEls.includes(contentEditableEl)) {\n                    contentEditableEl.setAttribute(\"contenteditable\", true);\n                }\n            }\n        }\n    }\n\n    cleanForSave({ root }) {\n        const toRemoveSelector = this.getResource(\"contenteditable_to_remove_selector\").join(\",\");\n        const contenteditableEls = toRemoveSelector\n            ? [...selectElements(root, toRemoveSelector)]\n            : [];\n        for (const contenteditableEl of contenteditableEls) {\n            contenteditableEl.removeAttribute(\"contenteditable\");\n        }\n    }\n}\n", "import { Plugin } from \"../plugin\";\nimport { closestBlock, isBlock } from \"../utils/blocks\";\nimport {\n    isAllowedContent,\n    isButton,\n    isContentEditable,\n    isEmpty,\n    isInPre,\n    isProtected,\n    isShrunkBlock,\n    isTangible,\n    isTextNode,\n    isVisibleTextNode,\n    isWhitespace,\n    isZwnbsp,\n    isZWS,\n    nextLeaf,\n    previousLeaf,\n    isEmptyBlock,\n} from \"../utils/dom_info\";\nimport { getState, isFakeLineBreak, observeMutations, prepareUpdate } from \"../utils/dom_state\";\nimport {\n    childNodes,\n    closestElement,\n    findUpTo,\n    descendants,\n    firstLeaf,\n    getCommonAncestor,\n    lastLeaf,\n    findFurthest,\n} from \"../utils/dom_traversal\";\nimport {\n    DIRECTIONS,\n    childNodeIndex,\n    endPos,\n    leftPos,\n    nodeSize,\n    rightPos,\n    startPos,\n} from \"../utils/position\";\nimport { CTYPES } from \"../utils/content_types\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { compareListTypes } from \"@html_editor/main/list/utils\";\nimport { hasTouch, isBrowserChrome, isMacOS } from \"@web/core/browser/feature_detection\";\nimport { normalizeDeepCursorPosition, normalizeFakeBR } from \"@html_editor/utils/selection\";\n\n/**\n * @typedef {Object} RangeLike\n * @property {Node} startContainer\n * @property {number} startOffset\n * @property {Node} endContainer\n * @property {number} endOffset\n */\n\n/** @typedef {import(\"@html_editor/core/selection_plugin\").EditorSelection} EditorSelection */\n\n/**\n * @typedef {Object} DeleteShared\n * @property { DeletePlugin['delete'] } delete\n * @property { DeletePlugin['deleteRange'] } deleteRange\n * @property { DeletePlugin['deleteSelection'] } deleteSelection\n * @property { DeletePlugin['deleteBackward'] } deleteBackward\n * @property { DeletePlugin['deleteForward'] } deleteForward\n */\n\n/**\n * @typedef {(() => void)[]} before_delete_handlers\n * @typedef {(() => void)[]} delete_handlers\n *\n * @typedef {((range: RangeLike) => void | true)[]} delete_backward_overrides\n * @typedef {((range: RangeLike) => void | true)[]} delete_backward_word_overrides\n * @typedef {((range: RangeLike) => void | true)[]} delete_backward_line_overrides\n * @typedef {((range: RangeLike) => void | true)[]} delete_forward_overrides\n * @typedef {((range: RangeLike) => void | true)[]} delete_forward_word_overrides\n * @typedef {((range: RangeLike) => void | true)[]} delete_forward_line_overrides\n * @typedef {((range: RangeLike) => void | true)[]} delete_range_overrides\n *\n * @typedef {((node: Node) => boolean)[]} functional_empty_node_predicates\n * @typedef {((node: Node) => boolean)[]} is_empty_predicates\n *\n * @typedef {((node: Node) => Node[])[]} removable_descendants_providers\n *\n * @typedef {CSSSelector[]} system_node_selectors\n */\n/**\n * The `root` argument is used by some predicates in which a node is\n * conditionally unremovable (e.g. a table cell is only removable if its\n * ancestor table is also being removed).\n * @typedef {((node: Node, root: HTMLElement) => boolean)[]} unremovable_node_predicates\n */\n\n// @todo @phoenix: move these predicates to different plugins\nexport const unremovableNodePredicates = [\n    (node) => node.classList?.contains(\"oe_unremovable\"),\n    // Monetary field\n    (node) => node.matches?.(\"[data-oe-type='monetary'] > span\"),\n];\n\nexport class DeletePlugin extends Plugin {\n    static dependencies = [\"baseContainer\", \"selection\", \"history\", \"input\", \"userCommand\"];\n    static id = \"delete\";\n    static shared = [\"deleteBackward\", \"deleteForward\", \"deleteRange\", \"deleteSelection\", \"delete\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            { id: \"deleteBackward\", run: () => this.delete(\"backward\", \"character\") },\n            { id: \"deleteForward\", run: () => this.delete(\"forward\", \"character\") },\n            { id: \"deleteBackwardWord\", run: () => this.delete(\"backward\", \"word\") },\n            { id: \"deleteForwardWord\", run: () => this.delete(\"forward\", \"word\") },\n            { id: \"deleteBackwardLine\", run: () => this.delete(\"backward\", \"line\") },\n            { id: \"deleteForwardLine\", run: () => this.delete(\"forward\", \"line\") },\n        ],\n        shortcuts: [\n            { hotkey: \"backspace\", commandId: \"deleteBackward\" },\n            { hotkey: \"delete\", commandId: \"deleteForward\" },\n            { hotkey: \"control+backspace\", commandId: \"deleteBackwardWord\" },\n            { hotkey: \"control+delete\", commandId: \"deleteForwardWord\" },\n            { hotkey: \"control+shift+backspace\", commandId: \"deleteBackwardLine\" },\n            { hotkey: \"control+shift+delete\", commandId: \"deleteForwardLine\" },\n        ],\n        /** Handlers */\n        beforeinput_handlers: [\n            withSequence(5, this.onBeforeInputInsertText.bind(this)),\n            this.onBeforeInputDelete.bind(this),\n        ],\n        input_handlers: (ev) => this.onAndroidChromeInput?.(ev),\n        selectionchange_handlers: withSequence(5, () => this.onAndroidChromeSelectionChange?.()),\n        /** Overrides */\n        delete_backward_overrides: withSequence(30, this.deleteBackwardUnmergeable.bind(this)),\n        delete_backward_word_overrides: withSequence(20, this.deleteBackwardUnmergeable.bind(this)),\n        delete_backward_line_overrides: this.deleteBackwardUnmergeable.bind(this),\n        delete_forward_overrides: withSequence(20, this.deleteForwardUnmergeable.bind(this)),\n        delete_forward_word_overrides: this.deleteForwardUnmergeable.bind(this),\n        delete_forward_line_overrides: this.deleteForwardUnmergeable.bind(this),\n\n        unremovable_node_predicates: unremovableNodePredicates,\n        invalid_for_base_container_predicates: (node) => this.isUnremovable(node, this.editable),\n    };\n\n    setup() {\n        this.findPreviousPosition = this.makeFindPositionFn(\"backward\");\n        this.findNextPosition = this.makeFindPositionFn(\"forward\");\n        if (isMacOS()) {\n            // Bypass the hotkey service for Alt+Backspace and Cmd+Backspace\n            // on macOS which would otherwise conflict with other shortcuts.\n            this.addDomListener(this.editable, \"keydown\", (event) => {\n                const runCommand = (commandId) => {\n                    this.dependencies.userCommand.getCommand(commandId).run();\n                    event.stopImmediatePropagation();\n                    event.preventDefault();\n                };\n                // Delete word backward: Option + Backspace\n                if (event.altKey && event.key === \"Backspace\") {\n                    return runCommand(\"deleteBackwardWord\");\n                }\n\n                // Delete word forward: Option + Delete\n                if (event.altKey && event.key === \"Delete\") {\n                    return runCommand(\"deleteForwardWord\");\n                }\n\n                // Delete line backward: Command + Backspace\n                if (event.metaKey && event.key === \"Backspace\") {\n                    return runCommand(\"deleteBackwardLine\");\n                }\n\n                // Delete line forward: Command + Delete\n                if (event.metaKey && event.key === \"Delete\") {\n                    return runCommand(\"deleteForwardLine\");\n                }\n            });\n        }\n    }\n\n    /**\n     * @param {EditorSelection} selection\n     * @returns {Range}\n     */\n    getNormalizedRange(selection) {\n        let { startContainer, startOffset, endContainer, endOffset, isCollapsed } = selection;\n        for (const normalizer of [normalizeDeepCursorPosition, normalizeFakeBR]) {\n            [startContainer, startOffset] = normalizer(startContainer, startOffset);\n            [endContainer, endOffset] = isCollapsed\n                ? [startContainer, startOffset]\n                : normalizer(endContainer, endOffset);\n        }\n        const range = this.document.createRange();\n        range.setStart(startContainer, startOffset);\n        range.setEnd(endContainer, endOffset);\n        return range;\n    }\n\n    // --------------------------------------------------------------------------\n    // commands\n    // --------------------------------------------------------------------------\n\n    /**\n     * @param {EditorSelection} [selection]\n     */\n    deleteSelection(selection = this.dependencies.selection.getEditableSelection()) {\n        // @todo @phoenix: handle non-collapsed selection around a ZWS\n        // see collapseIfZWS\n\n        let range = this.getNormalizedRange(selection);\n        if (range.collapsed) {\n            return;\n        }\n        // Delete only if the targeted nodes are all editable or if every\n        // non-editable node's editable ancestor is fully selected. We use the\n        // targeted nodes here to be sure to include a partial text node\n        // selection.\n        const selectedNodes = this.dependencies.selection.getTargetedNodes();\n        const canBeDeleted = (node) =>\n            this.dependencies.selection.isNodeEditable(node) ||\n            selectedNodes.includes(\n                closestElement(node, (node) => this.dependencies.selection.isNodeEditable(node))\n            );\n        if (selectedNodes.some((node) => !canBeDeleted(node))) {\n            return;\n        }\n        range = this.adjustRange(range, [\n            this.expandRangeToIncludeNonEditables,\n            this.includeEndOrStartBlock,\n            this.fullyIncludeLinks,\n        ]);\n\n        if (this.delegateTo(\"delete_range_overrides\", range)) {\n            return;\n        }\n\n        range = this.deleteRange(range);\n        this.setCursorFromRange(range);\n    }\n\n    /**\n     * @param {\"backward\"|\"forward\"} direction\n     * @param {\"character\"|\"word\"|\"line\"} granularity\n     */\n    delete(direction, granularity) {\n        const selection = this.dependencies.selection.getEditableSelection();\n        this.dispatchTo(\"before_delete_handlers\");\n\n        if (!selection.isCollapsed) {\n            this.deleteSelection(selection);\n        } else if (direction === \"backward\") {\n            this.deleteBackward(selection, granularity);\n        } else if (direction === \"forward\") {\n            this.deleteForward(selection, granularity);\n        } else {\n            throw new Error(\"Invalid direction\");\n        }\n        this.dispatchTo(\"delete_handlers\");\n        this.dependencies.history.addStep();\n    }\n\n    // --------------------------------------------------------------------------\n    // Delete backward/forward\n    // --------------------------------------------------------------------------\n\n    /**\n     * @param {EditorSelection} selection\n     * @param {\"character\"|\"word\"|\"line\"} granularity\n     */\n    deleteBackward(selection, granularity) {\n        const { endContainer, endOffset } = this.getNormalizedRange(selection);\n        if (!closestElement(endContainer).isContentEditable) {\n            return;\n        }\n\n        let range = this.getRangeForDelete(endContainer, endOffset, \"backward\", granularity);\n\n        const resourceIds = {\n            character: \"delete_backward_overrides\",\n            word: \"delete_backward_word_overrides\",\n            line: \"delete_backward_line_overrides\",\n        };\n        if (this.delegateTo(resourceIds[granularity], range)) {\n            return;\n        }\n\n        range = this.adjustRange(range, [\n            this.includeEmptyInlineEnd,\n            this.includePreviousZWS,\n            this.includeEndOrStartBlock,\n        ]);\n        range = this.deleteRange(range);\n        this.document.getSelection()?.removeAllRanges();\n        this.setCursorFromRange(range, { collapseToEnd: true });\n    }\n\n    /**\n     * @param {EditorSelection} selection\n     * @param {\"character\"|\"word\"|\"line\"} granularity\n     */\n    deleteForward(selection, granularity) {\n        const { startContainer, startOffset } = this.getNormalizedRange(selection);\n        if (!closestElement(startContainer).isContentEditable) {\n            return;\n        }\n\n        let range = this.getRangeForDelete(startContainer, startOffset, \"forward\", granularity);\n\n        const resourceIds = {\n            character: \"delete_forward_overrides\",\n            word: \"delete_forward_word_overrides\",\n            line: \"delete_forward_line_overrides\",\n        };\n        if (this.delegateTo(resourceIds[granularity], range)) {\n            return;\n        }\n\n        range = this.adjustRange(range, [\n            this.includeEmptyInlineStart,\n            this.includeNextZWS,\n            this.includeEndOrStartBlock,\n        ]);\n        range = this.deleteRange(range);\n        this.setCursorFromRange(range);\n    }\n\n    getRangeForDelete(node, offset, direction, granularity) {\n        let destContainer, destOffset;\n        if (granularity === \"word\") {\n            // In some browsers such as Firefox or Safari, if the cursor\n            // is at the start of a block (when direction is \"backward\") or\n            // at the end of a block (when direction is \"forward\"),\n            // Selection.modify(\"extend\", direction, \"word\") ends up\n            // selecting the previous or next adjacent text node, respectively.\n            // To handle such cases, the granularity should be \"character\".\n            const blockEl = closestBlock(node);\n            if (\n                (direction === \"backward\" &&\n                    this.isCursorAtStartOfElement(blockEl, node, offset)) ||\n                (direction === \"forward\" && this.isCursorAtEndOfElement(blockEl, node, offset))\n            ) {\n                granularity = \"character\";\n            }\n        }\n        switch (granularity) {\n            case \"character\":\n                [destContainer, destOffset] = this.findAdjacentPosition(node, offset, direction);\n                break;\n            case \"word\":\n                ({ focusNode: destContainer, focusOffset: destOffset } =\n                    this.dependencies.selection.modifySelection(\"extend\", direction, \"word\"));\n                break;\n            case \"line\":\n                [destContainer, destOffset] = this.findLineBoundary(node, offset, direction);\n                break;\n            default:\n                throw new Error(\"Invalid granularity\");\n        }\n\n        if (!destContainer) {\n            [destContainer, destOffset] = [node, offset];\n        }\n        const [startContainer, startOffset, endContainer, endOffset] =\n            direction === \"forward\"\n                ? [node, offset, destContainer, destOffset]\n                : [destContainer, destOffset, node, offset];\n\n        return { startContainer, startOffset, endContainer, endOffset };\n    }\n\n    // --------------------------------------------------------------------------\n    // Delete range\n    // --------------------------------------------------------------------------\n\n    /*\n    Inline:\n        Empty inlines get filled, no joining.\n        <b>[abc]</b> -> <b>[]ZWS</b>\n        <b>[abc</b> <b>d]ef</b> -> <b>[]ZWS</b> <b>ef</b>\n        <b>[abc</b> <b>def]</b> -> <b>[]ZWS</b> <b>ZWS</b>\n\n    Block:\n        Shrunk blocks get filled.\n        <p>[abc]</p> -> <p>[]<br></p>\n\n        End block's content is appended to start block on join.\n        <h1>a[bc</h1> <p>de]f</p> -> <h1>a[]f</h1>\n        <h1>[abc</h1> <p>def]</p> -> <h1>[]<br></h1>\n\n        To make left block disappear instead, use this range:\n        [<h1>abc</h1> <p>de]f</p> -> []<p>f</p> (which can be normalized later, see setCursorFromRange)\n\n    Block + Inline:\n        Inline content after block is appended to block on join.\n        <p>a[bc</p> d]ef -> <p>a[]ef</p>\n\n    Inline + Block:\n        Block content is unwrapped on join.\n        ab[c <p>de]f</p> -> ab[]f\n        ab[c <p>de]f</p> ghi -> ab[]f<br>ghi\n\n    */\n\n    /**\n     * Removes (removable) nodes and merges block with block/inline when\n     * applicable (and mergeable).\n     * Returns the updated range, which is collapsed to start if the original\n     * range could be completely deleted and merged.\n     *\n     * @param {RangeLike} range\n     * @returns {RangeLike}\n     */\n    deleteRange(range) {\n        // Do nothing if the range is collapsed.\n        if (range.startContainer === range.endContainer && range.startOffset === range.endOffset) {\n            return range;\n        }\n        // Split text nodes in order to have elements as start/end containers.\n        range = this.splitTextNodes(range);\n\n        const { startContainer, startOffset, endContainer, endOffset } = range;\n        const restoreSpaces = prepareUpdate(startContainer, startOffset, endContainer, endOffset);\n\n        let restoreFakeBRs;\n        ({ restoreFakeBRs, range } = this.removeFakeBRs(range));\n\n        // Remove nodes.\n        let allNodesRemoved;\n        ({ allNodesRemoved, range } = this.removeNodes(range));\n\n        this.fillEmptyInlines(range);\n\n        // Join fragments.\n        const originalCommonAncestor = range.commonAncestorContainer;\n        if (allNodesRemoved) {\n            range = this.joinFragments(range);\n        }\n\n        restoreFakeBRs();\n        this.fillShrunkBlocks(originalCommonAncestor);\n        restoreSpaces();\n\n        return range;\n    }\n\n    splitTextNodes({ startContainer, startOffset, endContainer, endOffset }) {\n        // Splits text nodes only if necessary.\n        const split = (textNode, offset) => {\n            let didSplit = false;\n            if (offset === 0) {\n                offset = childNodeIndex(textNode);\n            } else if (offset === nodeSize(textNode)) {\n                offset = childNodeIndex(textNode) + 1;\n            } else {\n                textNode.splitText(offset);\n                didSplit = true;\n                offset = childNodeIndex(textNode) + 1;\n            }\n            return [textNode.parentElement, offset, didSplit];\n        };\n\n        if (endContainer.nodeType === Node.TEXT_NODE) {\n            [endContainer, endOffset] = split(endContainer, endOffset);\n        }\n        if (startContainer.nodeType === Node.TEXT_NODE) {\n            let didSplit;\n            [startContainer, startOffset, didSplit] = split(startContainer, startOffset);\n            if (startContainer === endContainer && didSplit) {\n                endOffset += 1;\n            }\n        }\n\n        return {\n            startContainer,\n            startOffset,\n            endContainer,\n            endOffset,\n            commonAncestorContainer: getCommonAncestor(\n                [startContainer, endContainer],\n                this.editable\n            ),\n        };\n    }\n\n    // Removes fake line breaks, so that each BR left is an actual line break.\n    // Returns the updated range and a function to later restore the fake BRs.\n    removeFakeBRs(range) {\n        let { startContainer, startOffset, endContainer, endOffset, commonAncestorContainer } =\n            range;\n        const visitedNodes = new Set();\n        const removeBRs = (container, offset) => {\n            let node = container;\n            while (node !== commonAncestorContainer) {\n                const lastBR = childNodes(node).findLast((child) => child.nodeName === \"BR\");\n                if (lastBR && isFakeLineBreak(lastBR)) {\n                    if (lastBR === container) {\n                        [container, offset] = leftPos(lastBR);\n                    } else if (node === container && offset > childNodeIndex(lastBR)) {\n                        offset -= 1;\n                    }\n                    lastBR.remove();\n                }\n                visitedNodes.add(node);\n                node = node.parentNode;\n            }\n            return [container, offset];\n        };\n        [startContainer, startOffset] = removeBRs(startContainer, startOffset);\n        [endContainer, endOffset] = removeBRs(endContainer, endOffset);\n        range = { startContainer, startOffset, endContainer, endOffset, commonAncestorContainer };\n\n        const restoreFakeBRs = () => {\n            for (const node of visitedNodes) {\n                if (!node.isConnected) {\n                    continue;\n                }\n                const lastBR = childNodes(node).findLast((child) => child.nodeName === \"BR\");\n                if (lastBR && isFakeLineBreak(lastBR)) {\n                    lastBR.after(this.document.createElement(\"br\"));\n                }\n                // Shrunk blocks are restored by `fillShrunkBlocks`.\n            }\n        };\n\n        return { restoreFakeBRs, range };\n    }\n\n    fillEmptyInlines(range) {\n        const nodes = [range.startContainer];\n        if (range.endContainer !== range.startContainer) {\n            nodes.push(range.endContainer);\n        }\n        for (const node of nodes) {\n            // @todo: mind Icons?\n            // Probably need to get deepest position's element\n            // @todo: update fillEmpty\n            if (!isBlock(node) && !isTangible(node) && !isZWS(node) && !isZwnbsp(node)) {\n                node.appendChild(this.document.createTextNode(\"\\u200B\"));\n                node.setAttribute(\"data-oe-zws-empty-inline\", \"\");\n            }\n        }\n    }\n\n    fillShrunkBlocks(commonAncestor) {\n        const fillBlock = (block) => {\n            if (\n                block.matches(\"div[contenteditable='true']\") &&\n                !block.parentElement.isContentEditable\n            ) {\n                // @todo: not sure we want this when allowInlineAtRoot is true\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                baseContainer.appendChild(this.document.createElement(\"br\"));\n                block.appendChild(baseContainer);\n            } else {\n                block.appendChild(this.document.createElement(\"br\"));\n            }\n        };\n        // @todo: this ends up filling shrunk blocks outside the affected range.\n        // Ideally, it should only affect the block within the boundaries of the\n        // original range.\n        for (const node of descendants(commonAncestor).reverse()) {\n            if (isBlock(node) && isShrunkBlock(node)) {\n                fillBlock(node);\n            }\n        }\n        const containingBlock = closestBlock(commonAncestor);\n        if (isShrunkBlock(containingBlock)) {\n            fillBlock(containingBlock);\n        }\n    }\n\n    // --------------------------------------------------------------------------\n    // Remove nodes\n    // --------------------------------------------------------------------------\n\n    removeNodes(range) {\n        const { startContainer, startOffset, endContainer, commonAncestorContainer } = range;\n        let { endOffset } = range;\n        const nodesToRemove = [];\n\n        // Pick child nodes to the right for later removal, propagate until\n        // commonAncestorContainer (non-inclusive)\n        let node = startContainer;\n        let startRemoveIndex = startOffset;\n        while (node !== commonAncestorContainer) {\n            for (let i = startRemoveIndex; i < node.childNodes.length; i++) {\n                nodesToRemove.push(node.childNodes[i]);\n            }\n            startRemoveIndex = childNodeIndex(node) + 1;\n            node = node.parentElement;\n        }\n\n        // Pick child nodes to the left for later removal, propagate until\n        // commonAncestorContainer (non-inclusive)\n        node = endContainer;\n        let endRemoveIndex = endOffset;\n        while (node !== commonAncestorContainer) {\n            for (let i = 0; i < endRemoveIndex; i++) {\n                nodesToRemove.push(node.childNodes[i]);\n            }\n            endRemoveIndex = childNodeIndex(node);\n            node = node.parentElement;\n        }\n\n        // Pick commonAncestorContainer's direct children for removal\n        for (let i = startRemoveIndex; i < endRemoveIndex; i++) {\n            nodesToRemove.push(commonAncestorContainer.childNodes[i]);\n        }\n\n        // Remove nodes\n        let allNodesRemoved = true;\n        for (const node of nodesToRemove) {\n            const parent = node.parentNode;\n            const didRemove = this.removeNode(node);\n            allNodesRemoved &&= didRemove;\n            if (didRemove && endContainer === parent) {\n                endOffset -= 1;\n            }\n        }\n\n        const endContainerList = closestElement(endContainer, \"UL, OL\");\n        if (\n            [\"OL\", \"UL\"].includes(startContainer.nodeName) &&\n            endContainerList &&\n            !compareListTypes(startContainer, endContainerList)\n        ) {\n            const newRange = this.document.createRange();\n            newRange.setStart(range.endContainer, endOffset);\n            return { allNodesRemoved, range: newRange };\n        }\n        return { allNodesRemoved, range: { ...range, endOffset } };\n    }\n\n    // The root argument is used by some predicates in which a node is\n    // conditionally unremovable (e.g. a table cell is only removable if its\n    // ancestor table is also being removed).\n    isUnremovable(node, root = undefined) {\n        return this.getResource(\"unremovable_node_predicates\").some((p) => p(node, root));\n    }\n\n    // Returns true if the entire subtree rooted at node was removed.\n    // Unremovable nodes take the place of removable ancestors.\n    removeNode(node) {\n        const root = node;\n        const remove = (node) => {\n            let customHandling = false;\n            let customIsUnremovable;\n            for (const cb of this.getResource(\"removable_descendants_providers\")) {\n                const descendantsToRemove = cb(node);\n                if (descendantsToRemove) {\n                    for (const descendant of descendantsToRemove) {\n                        remove(descendant);\n                    }\n                    customHandling = true;\n                    customIsUnremovable = this.isUnremovable(node, root);\n                    if (!customIsUnremovable) {\n                        // TODO ABD: test protected + unremovable\n                        node.remove();\n                    }\n                }\n            }\n            if (customHandling) {\n                return !customIsUnremovable;\n            }\n            for (const child of [...node.childNodes]) {\n                remove(child);\n            }\n            if (this.isUnremovable(node, root)) {\n                return false;\n            }\n            if (node.hasChildNodes()) {\n                node.before(...node.childNodes);\n                node.remove();\n                return false;\n            }\n            node.remove();\n            return true;\n        };\n        return remove(node);\n    }\n\n    // --------------------------------------------------------------------------\n    // Join\n    // --------------------------------------------------------------------------\n\n    // Joins both ends of the range if possible: block + block/inline.\n    // If joined, the range is collapsed to start.\n    // Returns the updated range.\n    joinFragments(range) {\n        const joinableLeft = this.getJoinableFragment(range, \"start\");\n        const joinableRight = this.getJoinableFragment(range, \"end\");\n        const join = this.getJoinOperation(joinableLeft.type, joinableRight.type);\n\n        const didJoin = join(joinableLeft.node, joinableRight.node, range.commonAncestorContainer);\n\n        return didJoin ? this.collapseRange(range) : range;\n    }\n\n    /**\n     * Retrieves the joinable fragment based on the given range and side.\n     *\n     * @param {Object} range - range-like object.\n     * @param {\"start\"|\"end\"} side\n     * @returns {Object} - { node: Node|null, type: \"block\"|\"inline\"|\"null\" }\n     */\n    getJoinableFragment(range, side) {\n        const commonAncestor = range.commonAncestorContainer;\n        const container = side === \"start\" ? range.startContainer : range.endContainer;\n        const offset = side === \"start\" ? range.startOffset : range.endOffset;\n\n        if (container === range.commonAncestorContainer) {\n            // This means a direct child of the commonAncestor was removed.\n            // The joinable in this case is its sibling (previous for the start\n            // side, next for the end side), but only if inline.\n            const sibling = childNodes(commonAncestor)[side === \"start\" ? offset - 1 : offset];\n            if (\n                sibling &&\n                !isBlock(sibling) &&\n                !(sibling.nodeType === Node.TEXT_NODE && !isVisibleTextNode(sibling))\n            ) {\n                return { node: sibling, type: \"inline\" };\n            }\n            // No fragment to join.\n            return { node: null, type: \"null\" };\n        }\n        // Starting from `container`, find the closest block up to\n        // (not-inclusive) the common ancestor. If not found, keep the common\n        // ancestor's child inline element.\n        let last;\n        let element = container;\n        while (element !== commonAncestor) {\n            if (isBlock(element)) {\n                return { node: element, type: \"block\" };\n            }\n            last = element;\n            element = element.parentElement;\n        }\n        return { node: last, type: \"inline\" };\n    }\n\n    getJoinOperation(leftType, rightType) {\n        return (\n            {\n                \"block + block\": this.joinBlocks,\n                \"block + inline\": this.joinInlineIntoBlock,\n                \"inline + block\": this.joinBlockIntoInline,\n            }[leftType + \" + \" + rightType] || (() => true)\n        ).bind(this);\n        // \"inline + inline\": Nothing to do, consider it joined.\n        // Same any combination involving type \"null\" (no joinable element).\n    }\n\n    /**\n     * An unsplittable element is also unmergeable and vice-versa (as split and\n     * merge are reverse operations from one another).\n     */\n    isUnmergeable(node) {\n        return this.getResource(\"unsplittable_node_predicates\").some((p) => p(node));\n    }\n\n    joinBlocks(left, right, commonAncestor) {\n        // Check if both blocks are mergeable.\n        const canMerge = (n) => !findUpTo(n, commonAncestor, this.isUnmergeable.bind(this));\n        if (!canMerge(left) || !canMerge(right)) {\n            return false;\n        }\n\n        // Check if left block allows right block's content.\n        const rightChildNodes = childNodes(right);\n        if (!isAllowedContent(left, rightChildNodes)) {\n            return false;\n        }\n\n        left.append(...rightChildNodes);\n        let toRemove = right;\n        let parent = right.parentElement;\n        // Propagate until commonAncestor, removing empty blocks\n        while (parent !== commonAncestor && parent.childNodes.length === 1) {\n            toRemove = parent;\n            parent = parent.parentElement;\n        }\n        toRemove.remove();\n        return true;\n    }\n\n    joinInlineIntoBlock(leftBlock, rightInline, commonAncestor) {\n        if (findUpTo(leftBlock, commonAncestor, (node) => this.isUnmergeable(node))) {\n            // Left block is unmergeable.\n            return false;\n        }\n\n        // @todo: avoid appending a BR as last child of the block\n        while (rightInline && !isBlock(rightInline)) {\n            const toAppend = rightInline;\n            rightInline = rightInline.nextSibling;\n            leftBlock.append(toAppend);\n        }\n        return true;\n    }\n\n    joinBlockIntoInline(leftInline, rightBlock, commonAncestor) {\n        if (findUpTo(rightBlock, commonAncestor, (node) => this.isUnmergeable(node))) {\n            // Right block is unmergeable.\n            return false;\n        }\n\n        leftInline.after(...childNodes(rightBlock));\n        let toRemove = rightBlock;\n        let parent = rightBlock.parentElement;\n        // Propagate until commonAncestor, removing empty blocks\n        while (parent !== commonAncestor && parent.childNodes.length === 1) {\n            toRemove = parent;\n            parent = parent.parentElement;\n        }\n        // Restore line break between removed block and inline content after it.\n        if (parent === commonAncestor) {\n            const rightSibling = toRemove.nextSibling;\n            if (rightSibling && !isBlock(rightSibling)) {\n                rightSibling.before(this.document.createElement(\"br\"));\n            }\n        }\n        toRemove.remove();\n        return true;\n    }\n\n    // --------------------------------------------------------------------------\n    // Adjust range\n    // --------------------------------------------------------------------------\n\n    /**\n     * @param {RangeLike}\n     * @param {((range: Range) => Range)[]} callbacks\n     * @returns {RangeLike}\n     */\n    adjustRange({ startContainer, startOffset, endContainer, endOffset }, callbacks) {\n        let range = this.document.createRange();\n        range.setStart(startContainer, startOffset);\n        range.setEnd(endContainer, endOffset);\n\n        for (const callback of callbacks) {\n            range = callback.call(this, range);\n        }\n\n        ({ startContainer, startOffset, endOffset, endContainer } = range);\n        return { startContainer, startOffset, endOffset, endContainer };\n    }\n\n    /**\n     * <h1>[abc</h1><p>d]ef</p> -> [<h1>abc</h1><p>d]ef</p>\n     *\n     * @param {HTMLElement} block\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includeBlockStart(block, range) {\n        const { startContainer, startOffset, commonAncestorContainer } = range;\n        if (\n            block === commonAncestorContainer ||\n            !this.isCursorAtStartOfElement(block, startContainer, startOffset)\n        ) {\n            return range;\n        }\n        range.setStartBefore(block);\n        return this.includeBlockStart(block.parentNode, range);\n    }\n\n    /**\n     * <p>ab[c</p><div>def]</div> ->  <p>ab[c</p><div>def</div>]\n     *\n     * @param {HTMLElement} block\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includeBlockEnd(block, range) {\n        const { startContainer, endContainer, endOffset, commonAncestorContainer } = range;\n        const startList = closestElement(startContainer, \"UL, OL\");\n        const endList = closestElement(endContainer, \"UL, OL\");\n        if (\n            block === commonAncestorContainer ||\n            !this.isCursorAtEndOfElement(block, endContainer, endOffset) ||\n            (startList &&\n                endList &&\n                !compareListTypes(startList, endList) &&\n                !startList.contains(endList))\n        ) {\n            return range;\n        }\n        range.setEndAfter(block);\n        return this.includeBlockEnd(block.parentNode, range);\n    }\n\n    /**\n     * If range spans two blocks, try to fully include the right (end) one OR\n     * the left (start) one (but not both).\n     *\n     * E.g.:\n     * Fully includes the right block:\n     * <p>ab[c</p><div>def]</div> ->  <p>ab[c</p><div>def</div>]\n     * <p>[abc</p><div>def]</div> ->  <p>[abc</p><div>def</div>]\n     *\n     * Fully includes the left block:\n     * <h1>[abc</h1><p>d]ef</p> -> [<h1>abc</h1><p>d]ef</p>\n     *\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includeEndOrStartBlock(range) {\n        const { startContainer, endContainer, commonAncestorContainer } = range;\n        const startBlock = findUpTo(startContainer, commonAncestorContainer, isBlock);\n        const endBlock = findUpTo(endContainer, commonAncestorContainer, isBlock);\n        if (!startBlock || !endBlock) {\n            return range;\n        }\n        range = this.includeBlockEnd(endBlock, range);\n        // Only include start block if end block could not be included.\n        if (range.endContainer === endContainer) {\n            range = this.includeBlockStart(startBlock, range);\n        }\n        return range;\n    }\n\n    /**\n     * Fully select link if:\n     * - range spans content inside and outside the link AND\n     * - all of its content is selected.\n     *\n     * <a>[abc</a>d]ef -> [<a>abc</a>d]ef\n     * ab[c<a>def]</a> ->  ab[c<a>def</a>]\n     * But:\n     * <a>[abc]</a> -> <a>[abc]</a> (remains unchanged)\n     *\n     * @param {Range} range\n     * @returns {Range}\n     */\n    fullyIncludeLinks(range) {\n        const { startContainer, startOffset, endContainer, endOffset, commonAncestorContainer } =\n            range;\n        const [startLink, endLink] = [startContainer, endContainer].map((container) =>\n            findUpTo(container, commonAncestorContainer, (node) => node.nodeName === \"A\")\n        );\n        if (startLink && this.isCursorAtStartOfElement(startLink, startContainer, startOffset)) {\n            range.setStartBefore(startLink);\n        }\n        if (endLink && this.isCursorAtEndOfElement(endLink, endContainer, endOffset)) {\n            range.setEndAfter(endLink);\n        }\n        return range;\n    }\n\n    /**\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includeEmptyInlineStart(range) {\n        const element = closestElement(range.startContainer);\n        if (this.isEmptyInline(element)) {\n            range.setStartBefore(element);\n        }\n        return range;\n    }\n\n    /**\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includeEmptyInlineEnd(range) {\n        const element = closestElement(range.endContainer);\n        if (this.isEmptyInline(element)) {\n            range.setEndAfter(element);\n        }\n        return range;\n    }\n\n    // @todo @phoenix This is here because of the second test case in\n    // delete/forward/selection collapsed/basic/should ignore ZWS, and its\n    // importance is questionable.\n    /**\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includeNextZWS(range) {\n        const { endContainer, endOffset } = range;\n        if (isTextNode(endContainer) && endContainer.textContent[endOffset] === \"\\u200B\") {\n            range.setEnd(endContainer, endOffset + 1);\n        }\n        return range;\n    }\n\n    /**\n     * @param {Range} range\n     * @returns {Range}\n     */\n    includePreviousZWS(range) {\n        const { startContainer, startOffset } = range;\n        if (\n            isTextNode(startContainer) &&\n            startContainer.textContent[startOffset - 1] === \"\\u200B\"\n        ) {\n            range.setStart(startContainer, startOffset - 1);\n        }\n        return range;\n    }\n\n    /**\n     * Expand the range to fully include all contentEditable=False elements.\n     * This scenario happens when the range has one end inside a non-editable\n     * element and the other end outside of it.\n     *\n     * @param {Range} range\n     * @returns {Range}\n     */\n    expandRangeToIncludeNonEditables(range) {\n        const {\n            startContainer,\n            startOffset,\n            endContainer,\n            endOffset,\n            commonAncestorContainer: commonAncestor,\n        } = range;\n        const isNonEditable = (node) => !isContentEditable(node);\n        const startUneditable =\n            startOffset === 0 &&\n            !previousLeaf(startContainer, closestBlock(startContainer)) &&\n            findFurthest(startContainer, commonAncestor, isNonEditable);\n        if (startUneditable) {\n            range.setStartBefore(startUneditable);\n        }\n        const endUneditable =\n            endOffset === nodeSize(endContainer) &&\n            !nextLeaf(endContainer, closestBlock(endContainer)) &&\n            findFurthest(endContainer, commonAncestor, isNonEditable);\n        if (endUneditable) {\n            range.setEndAfter(endUneditable);\n        }\n        return range;\n    }\n\n    // --------------------------------------------------------------------------\n    // Find previous/next position\n    // --------------------------------------------------------------------------\n\n    /**\n     * Returns the next/previous position for deletion.\n     *\n     * @param {Node} node\n     * @param {number} offset\n     * @param {\"forward\"|\"backward\"} direction\n     * @returns {[Node|null, Number|null]}\n     */\n    findAdjacentPosition(node, offset, direction) {\n        return direction === \"forward\"\n            ? this.findNextPosition(node, offset)\n            : this.findPreviousPosition(node, offset);\n    }\n\n    /**\n     *  Returns a function to find the adjacent position in the given direction.\n     *\n     * @param {\"forward\"|\"backward\"} direction\n     */\n    makeFindPositionFn(direction) {\n        const isDirectionForward = direction === \"forward\";\n\n        // Define helper functions based on the direction.\n        // Text node helpers.\n        const findVisibleChar = (\n            isDirectionForward ? this.findNextVisibleChar : this.findPreviousVisibleChar\n        ).bind(this);\n        const charLeftPos = (index, char) => index;\n        const charRightPos = (index, char) => index + char.length;\n        const indexBeforeChar = isDirectionForward ? charLeftPos : charRightPos;\n        const indexAfterChar = isDirectionForward ? charRightPos : charLeftPos;\n        const textEdgePos = isDirectionForward ? startPos : endPos;\n        // Leaf helpers.\n        const adjacentLeaf = (isDirectionForward ? this.nextLeaf : this.previousLeaf).bind(this);\n        const adjacentLeafFromPos = (\n            isDirectionForward ? this.nextLeafFromPos : this.previousLeafFromPos\n        ).bind(this);\n        const beforePos = isDirectionForward ? leftPos : rightPos;\n        const afterPos = isDirectionForward ? rightPos : leftPos;\n\n        /**\n         * Returns the next/previous position for deletion.\n         *\n         * \"Before\" and \"after\" have different meanings depending on the\n         * direction: before and after mean, respectively, previous and next in\n         * DOM order when direction is \"forward\", and the other way around when\n         * direction is \"backward\".\n         *\n         * @param {Node} node\n         * @param {number} offset\n         * @returns {[Node|null, Number|null]}\n         */\n        return function findPosition(node, offset) {\n            if (node.nodeType === Node.TEXT_NODE) {\n                const [char, index] = findVisibleChar(node, offset);\n                if (char) {\n                    return [node, indexAfterChar(index, char)];\n                }\n            }\n\n            // Define context: search is restricted to the closest editable root.\n            const isEditableRoot = (n) => n.isContentEditable && !n.parentNode.isContentEditable;\n            const editableRoot = findUpTo(node, this.editable.parentNode, isEditableRoot);\n\n            let blockSwitch;\n            const nodeClosestBlock = closestBlock(node);\n            let leaf = adjacentLeafFromPos(node, offset, editableRoot);\n            while (leaf) {\n                const leafClosestBlock = closestBlock(leaf);\n                blockSwitch ||= leafClosestBlock !== nodeClosestBlock;\n\n                if (this.shouldSkip(leaf, blockSwitch)) {\n                    leaf = adjacentLeaf(leaf, editableRoot);\n                    continue;\n                }\n\n                if (\n                    leaf.nodeType === Node.TEXT_NODE &&\n                    !(blockSwitch && isEmptyBlock(leafClosestBlock))\n                ) {\n                    const [char, index] = findVisibleChar(...textEdgePos(leaf));\n                    if (char) {\n                        const idx = (blockSwitch ? indexBeforeChar : indexAfterChar)(index, char);\n                        return [leaf, idx];\n                    }\n                } else if (!leaf.isContentEditable && isBlock(leaf)) {\n                    // E.g. Desired range for deleteForward:\n                    // <p>abc[</p><div contenteditable=\"false\">def</div>]<p>ghi</p>\n                    return afterPos(leaf);\n                } else {\n                    return blockSwitch ? beforePos(leaf) : afterPos(leaf);\n                }\n                leaf = adjacentLeaf(leaf, editableRoot);\n            }\n            return [null, null];\n        };\n    }\n\n    findLineBoundary(container, offset, direction) {\n        const adjacentLeaf = direction === \"forward\" ? nextLeaf : previousLeaf;\n        const edgeIndex = (node) => (direction === \"forward\" ? nodeSize(node) : 0);\n        const block = closestBlock(container);\n        let last = container;\n        let node = adjacentLeaf(container, this.editable);\n        // look for a BR or a block start\n        while (node && node.nodeName !== \"BR\" && closestBlock(node) === block) {\n            last = node;\n            node = adjacentLeaf(node, this.editable);\n        }\n        if (last === container && offset === edgeIndex(container)) {\n            // Cursor is already next to the line break, go to following position.\n            return this.findAdjacentPosition(container, offset, direction);\n        }\n        return direction === \"forward\" ? rightPos(last) : leftPos(last);\n    }\n\n    // @todo @phoenix: there are not enough tests for visibility of characters\n    // (invisible whitespace, separate nodes, etc.)\n    isVisibleChar(char, textNode, offset) {\n        // Protected nodes are always \"visible\" for the editor\n        if (isProtected(textNode)) {\n            // TODO ABD: add test\n            return true;\n        }\n        const isZwnbspLinkPad = (node) =>\n            isButton(node.previousSibling) || isButton(node.nextSibling);\n        if (isZwnbsp(textNode) && isZwnbspLinkPad(textNode)) {\n            return true;\n        }\n        // ZWS and ZWNBSP are invisible.\n        if ([\"\\u200B\", \"\\uFEFF\"].includes(char)) {\n            return false;\n        }\n        if (!isWhitespace(char) || isInPre(textNode)) {\n            return true;\n        }\n\n        // Assess visibility of whitespace.\n        // Whitespace is visible if it's immediately preceded by content, and\n        // followed by content before a BR or block start/end.\n\n        // If not preceded by content, it is invisible.\n        if (offset) {\n            return !isWhitespace(textNode.textContent[offset - char.length]);\n        } else if (!(getState(...leftPos(textNode), DIRECTIONS.LEFT).cType & CTYPES.CONTENT)) {\n            return false;\n        }\n\n        // Space is only visible if it's followed by content (with an optional\n        // sequence of invisible spaces in between), before a BR or block\n        // end/start.\n        const charsToTheRight = textNode.textContent.slice(offset + char.length);\n        for (char of charsToTheRight) {\n            if (!isWhitespace(char)) {\n                return true;\n            }\n        }\n        // No content found in text node, look to the right of it\n        if (getState(...rightPos(textNode), DIRECTIONS.RIGHT).cType & CTYPES.CONTENT) {\n            return true;\n        }\n\n        return false;\n    }\n\n    shouldSkip(leaf, blockSwitch) {\n        // A system node is a node that should be ignored by the editor. In\n        // other words, if the editor had a VDOM, it would be absent from it.\n        const systemNodeSelectors = this.getResource(\"system_node_selectors\").join(\",\");\n        if (systemNodeSelectors && closestElement(leaf, systemNodeSelectors)) {\n            return true;\n        }\n        if (leaf.nodeType === Node.TEXT_NODE) {\n            return false;\n        }\n        // @todo Maybe skip anything that is not an element (e.g. comment nodes)\n        if (blockSwitch) {\n            return false;\n        }\n        if (leaf.nodeName === \"BR\" && isFakeLineBreak(leaf)) {\n            return true;\n        }\n        if (\n            this.getResource(\"functional_empty_node_predicates\").some((predicate) =>\n                predicate(leaf)\n            )\n        ) {\n            return false;\n        }\n        if (isEmpty(leaf) || isZWS(leaf)) {\n            return true;\n        }\n        return false;\n    }\n\n    findPreviousVisibleChar(textNode, index) {\n        // @todo @phoenix: write tests for chars with size > 1 (emoji, etc.)\n        // Use the string iterator to handle surrogate pairs.\n        const chars = [...textNode.textContent.slice(0, index)];\n        let char = chars.pop();\n        while (char) {\n            index -= char.length;\n            if (this.isVisibleChar(char, textNode, index)) {\n                return [char, index];\n            }\n            char = chars.pop();\n        }\n        return [null, null];\n    }\n\n    findNextVisibleChar(textNode, index) {\n        // Use the string iterator to handle surrogate pairs.\n        for (const char of textNode.textContent.slice(index)) {\n            if (this.isVisibleChar(char, textNode, index)) {\n                return [char, index];\n            }\n            index += char.length;\n        }\n        return [null, null];\n    }\n\n    // If leaf is part of a contenteditable=false tree, consider its root as the\n    // leaf instead.\n    adjustedLeaf(leaf, refEditableRoot) {\n        const isNonEditable = (node) => !isContentEditable(node);\n        const nonEditableRoot = leaf && findFurthest(leaf, refEditableRoot, isNonEditable);\n        return nonEditableRoot || leaf;\n    }\n\n    previousLeaf(node, editableRoot) {\n        return this.adjustedLeaf(previousLeaf(node, editableRoot), editableRoot);\n    }\n\n    nextLeaf(node, editableRoot) {\n        return this.adjustedLeaf(nextLeaf(node, editableRoot), editableRoot);\n    }\n\n    previousLeafFromPos(node, offset, editableRoot) {\n        const leaf =\n            node.hasChildNodes() && offset > 0\n                ? lastLeaf(node.childNodes[offset - 1])\n                : previousLeaf(node, editableRoot);\n        return this.adjustedLeaf(leaf, editableRoot);\n    }\n\n    nextLeafFromPos(node, offset, editableRoot) {\n        const leaf =\n            node.hasChildNodes() && offset < nodeSize(node)\n                ? firstLeaf(node.childNodes[offset])\n                : nextLeaf(node, editableRoot);\n        return this.adjustedLeaf(leaf, editableRoot);\n    }\n\n    // --------------------------------------------------------------------------\n    // Event handlers\n    // --------------------------------------------------------------------------\n\n    onBeforeInputDelete(ev) {\n        const handledInputTypes = {\n            deleteContentBackward: [\"backward\", \"character\"],\n            deleteContentForward: [\"forward\", \"character\"],\n            deleteWordBackward: [\"backward\", \"word\"],\n            deleteWordForward: [\"forward\", \"word\"],\n            deleteHardLineBackward: [\"backward\", \"line\"],\n            deleteHardLineForward: [\"forward\", \"line\"],\n        };\n        const argsForDelete = handledInputTypes[ev.inputType];\n        if (argsForDelete) {\n            this.delete(...argsForDelete);\n            ev.preventDefault();\n            if (isBrowserChrome() && hasTouch()) {\n                this.preventDefaultDeleteAndroidChrome(ev);\n            }\n        }\n    }\n\n    onBeforeInputInsertText(ev) {\n        if (ev.inputType === \"insertText\") {\n            const selection = this.dependencies.selection.getSelectionData().deepEditableSelection;\n            if (!selection.isCollapsed) {\n                this.dispatchTo(\"before_delete_handlers\");\n                this.deleteSelection(selection);\n                this.dispatchTo(\"delete_handlers\");\n            }\n            // Default behavior: insert text and trigger input event\n        }\n    }\n\n    /**\n     * Beforeinput event of type deleteContentBackward cannot be default\n     * prevented in Android Chrome. So we need to revert:\n     * - eventual mutations between beforeinput and input events\n     * - eventual selection change after input event\n     *\n     * @param {InputEvent} beforeInputEvent\n     */\n    preventDefaultDeleteAndroidChrome(beforeInputEvent) {\n        const restoreDOM = this.dependencies.history.makeSavePoint();\n        this.onAndroidChromeInput = (ev) => {\n            if (ev.inputType !== beforeInputEvent.inputType) {\n                return;\n            }\n            // Revert DOM changes that occurred between beforeinput and input.\n            restoreDOM();\n\n            // Revert selection changes after input event, within the same tick.\n            // If further mutations occurred, consider selection change legit\n            // (e.g. dictionary input) and do not revert it.\n            const { restore: restoreSelection } = this.dependencies.selection.preserveSelection();\n            const observerOptions = { childList: true, subtree: true, characterData: true };\n            const getMutationRecords = observeMutations(this.editable, observerOptions);\n            this.onAndroidChromeSelectionChange = () => {\n                const shouldRevertSelectionChanges = !getMutationRecords().length;\n                if (shouldRevertSelectionChanges) {\n                    restoreSelection();\n                }\n            };\n            setTimeout(() => delete this.onAndroidChromeSelectionChange);\n        };\n    }\n\n    // ======== AD-HOC STUFF ========\n\n    deleteBackwardUnmergeable(range) {\n        const { startContainer, startOffset, endContainer, endOffset } = range;\n        return this.deleteCharUnmergeable(endContainer, endOffset, startContainer, startOffset);\n    }\n\n    // @todo @phoenix: write tests for this\n    deleteForwardUnmergeable(range) {\n        const { startContainer, startOffset, endContainer, endOffset } = range;\n        return this.deleteCharUnmergeable(startContainer, startOffset, endContainer, endOffset);\n    }\n\n    // Trap cursor inside unmergeable element. Remove it if empty.\n    deleteCharUnmergeable(sourceContainer, sourceOffset, destContainer, destOffset) {\n        if (!destContainer) {\n            return;\n        }\n        const commonAncestor = getCommonAncestor([sourceContainer, destContainer], this.editable);\n        const closestUnmergeable = findUpTo(sourceContainer, commonAncestor, (node) =>\n            this.isUnmergeable(node)\n        );\n        if (!closestUnmergeable) {\n            return;\n        }\n\n        if (\n            (isEmpty(closestUnmergeable) ||\n                this.getResource(\"is_empty_predicates\").some((p) => p(closestUnmergeable))) &&\n            !this.isUnremovable(closestUnmergeable)\n        ) {\n            closestUnmergeable.remove();\n            this.dependencies.selection.setSelection({\n                anchorNode: destContainer,\n                anchorOffset: destOffset,\n            });\n        } else {\n            this.dependencies.selection.setSelection({\n                anchorNode: sourceContainer,\n                anchorOffset: sourceOffset,\n            });\n        }\n        return true;\n    }\n\n    // --------------------------------------------------------------------------\n    // utils\n    // --------------------------------------------------------------------------\n\n    isEmptyInline(element) {\n        if (isBlock(element)) {\n            return false;\n        }\n        if (isZWS(element)) {\n            return true;\n        }\n        return element.innerHTML.trim() === \"\";\n    }\n\n    isCursorAtStartOfElement(element, cursorNode, cursorOffset) {\n        const [node] = this.findPreviousPosition(cursorNode, cursorOffset);\n        return !element.contains(node);\n    }\n\n    isCursorAtEndOfElement(element, cursorNode, cursorOffset) {\n        const [node] = this.findNextPosition(cursorNode, cursorOffset);\n        return !element.contains(node);\n    }\n\n    /**\n     * @param {RangeLike} range\n     */\n    setCursorFromRange(range, { collapseToEnd = false } = {}) {\n        range = this.collapseRange(range, { toEnd: collapseToEnd });\n        const [anchorNode, anchorOffset] = this.normalizeEnterBlock(\n            range.startContainer,\n            range.startOffset\n        );\n        this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n    }\n\n    // @todo: no need for this once selection in the editable root is corrected?\n    normalizeEnterBlock(node, offset) {\n        while (isBlock(node.childNodes[offset])) {\n            [node, offset] = [node.childNodes[offset], 0];\n        }\n        return [node, offset];\n    }\n\n    /**\n     * @param {RangeLike} range\n     */\n    collapseRange(range, { toEnd = false } = {}) {\n        let { startContainer, startOffset, endContainer, endOffset } = range;\n        if (toEnd) {\n            [startContainer, startOffset] = [endContainer, endOffset];\n        } else {\n            [endContainer, endOffset] = [startContainer, startOffset];\n        }\n        const commonAncestorContainer = startContainer;\n        return { startContainer, startOffset, endContainer, endOffset, commonAncestorContainer };\n    }\n}\n", "import { Plugin } from \"../plugin\";\n\n/**\n * @typedef {typeof import(\"@odoo/owl\").Component} Component\n * @typedef {import(\"@web/core/dialog/dialog_service\").DialogServiceInterfaceAddOptions} DialogServiceInterfaceAddOptions\n */\n\n/**\n * @typedef {Object} DialogShared\n * @property {DialogPlugin['addDialog']} addDialog\n */\n\nexport class DialogPlugin extends Plugin {\n    static id = \"dialog\";\n    static dependencies = [\"selection\"];\n    static shared = [\"addDialog\"];\n\n    /**\n     * @param {Component} DialogClass\n     * @param {Object} props\n     * @param {DialogServiceInterfaceAddOptions} options\n     * @returns {Promise<void>}\n     */\n    addDialog(DialogClass, props, options = {}) {\n        return new Promise((resolve) => {\n            this.services.dialog.add(DialogClass, props, {\n                onClose: () => {\n                    this.dependencies.selection.focusEditable();\n                    resolve();\n                },\n                ...options,\n            });\n        });\n    }\n}\n", "import { Plugin } from \"../plugin\";\nimport { closestBlock, isBlock } from \"../utils/blocks\";\nimport {\n    cleanTrailingBR,\n    fillEmpty,\n    fillShrunkPhrasingParent,\n    makeContentsInline,\n    removeClass,\n    removeStyle,\n    splitTextNode,\n    unwrapContents,\n    wrapInlinesInBlocks,\n} from \"../utils/dom\";\nimport {\n    allowsParagraphRelatedElements,\n    getDeepestPosition,\n    isContentEditable,\n    isContentEditableAncestor,\n    isEmptyBlock,\n    isListElement,\n    isListItemElement,\n    isParagraphRelatedElement,\n    isProtecting,\n    isProtected,\n    isSelfClosingElement,\n    isShrunkBlock,\n    isTangible,\n    isUnprotecting,\n    listElementSelector,\n    isEditorTab,\n    isPhrasingContent,\n} from \"../utils/dom_info\";\nimport {\n    childNodes,\n    children,\n    closestElement,\n    descendants,\n    firstLeaf,\n    lastLeaf,\n} from \"../utils/dom_traversal\";\nimport { FONT_SIZE_CLASSES, TEXT_STYLE_CLASSES } from \"../utils/formatting\";\nimport { DIRECTIONS, childNodeIndex, nodeSize, rightPos } from \"../utils/position\";\nimport { normalizeCursorPosition } from \"@html_editor/utils/selection\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\n/**\n * Get distinct connected parents of nodes\n *\n * @param {Iterable} nodes\n * @returns {Set}\n */\nfunction getConnectedParents(nodes) {\n    const parents = new Set();\n    for (const node of nodes) {\n        if (node.isConnected && node.parentElement) {\n            parents.add(node.parentElement);\n        }\n    }\n    return parents;\n}\n\n/**\n * @typedef {Object} DomShared\n * @property { DomPlugin['insert'] } insert\n * @property { DomPlugin['copyAttributes'] } copyAttributes\n * @property { DomPlugin['canSetBlock'] } canSetBlock\n * @property { DomPlugin['setBlock'] } setBlock\n * @property { DomPlugin['setTagName'] } setTagName\n * @property { DomPlugin['removeSystemProperties'] } removeSystemProperties\n */\n\n/**\n * @typedef {((insertedNodes: Node[]) => void)[]} after_insert_handlers\n * @typedef {((el: HTMLElement) => void)[]} before_set_tag_handlers\n *\n * @typedef {((insertedNode: Node) => insertedNode)[]} before_insert_processors\n * @typedef {((arg: { nodeToInsert: Node, container: HTMLElement }) => nodeToInsert)[]} node_to_insert_processors\n *\n * @typedef {string[]} system_attributes\n * @typedef {string[]} system_classes\n * @typedef {string[]} system_style_properties\n */\n\nexport class DomPlugin extends Plugin {\n    static id = \"dom\";\n    static dependencies = [\"baseContainer\", \"selection\", \"history\", \"split\", \"delete\", \"lineBreak\"];\n    static shared = [\n        \"insert\",\n        \"copyAttributes\",\n        \"canSetBlock\",\n        \"setBlock\",\n        \"setTagName\",\n        \"removeSystemProperties\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"insertFontAwesome\",\n                run: this.insertFontAwesome.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"setTag\",\n                run: this.setBlock.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        /** Handlers */\n        clean_for_save_handlers: ({ root }) => {\n            this.removeEmptyClassAndStyleAttributes(root);\n        },\n        clipboard_content_processors: this.removeEmptyClassAndStyleAttributes.bind(this),\n        functional_empty_node_predicates: [isSelfClosingElement, isEditorTab],\n    };\n\n    setup() {\n        this.systemClasses = this.getResource(\"system_classes\");\n        this.systemAttributes = this.getResource(\"system_attributes\");\n        this.systemStyleProperties = this.getResource(\"system_style_properties\");\n        this.systemPropertiesSelector = [\n            ...this.systemClasses.map((className) => `.${className}`),\n            ...this.systemAttributes.map((attr) => `[${attr}]`),\n            ...this.systemStyleProperties.map((prop) => `[style*=\"${prop}\"]`),\n        ].join(\",\");\n    }\n\n    // Shared\n\n    /**\n     * @param {string | DocumentFragment | Element | null} content\n     */\n    insert(content) {\n        if (!content) {\n            return;\n        }\n        let selection = this.dependencies.selection.getEditableSelection();\n        if (!selection.isCollapsed) {\n            this.dependencies.delete.deleteSelection();\n            selection = this.dependencies.selection.getEditableSelection();\n        }\n\n        let container = this.document.createElement(\"fake-element\");\n        const containerFirstChild = this.document.createElement(\"fake-element-fc\");\n        const containerLastChild = this.document.createElement(\"fake-element-lc\");\n        if (typeof content === \"string\") {\n            container.textContent = content;\n        } else {\n            if (content.nodeType === Node.ELEMENT_NODE) {\n                this.dispatchTo(\"normalize_handlers\", content);\n            } else {\n                for (const child of children(content)) {\n                    this.dispatchTo(\"normalize_handlers\", child);\n                }\n            }\n            container.replaceChildren(content);\n        }\n\n        const block = closestBlock(selection.anchorNode);\n        for (const cb of this.getResource(\"before_insert_processors\")) {\n            container = cb(container, block);\n        }\n        selection = this.dependencies.selection.getEditableSelection();\n\n        let startNode;\n        let insertBefore = false;\n        if (selection.startContainer.nodeType === Node.TEXT_NODE) {\n            insertBefore = !selection.startOffset;\n            splitTextNode(selection.startContainer, selection.startOffset, DIRECTIONS.LEFT);\n            startNode = selection.startContainer;\n        }\n\n        const allInsertedNodes = [];\n        // In case the html inserted starts with a list and will be inserted within\n        // a list, unwrap the list elements from the list.\n        const hasSingleChild = nodeSize(container) === 1;\n        if (\n            closestElement(selection.anchorNode, listElementSelector) &&\n            isListElement(container.firstChild)\n        ) {\n            unwrapContents(container.firstChild);\n        }\n        // Similarly if the html inserted ends with a list.\n        if (\n            closestElement(selection.focusNode, listElementSelector) &&\n            isListElement(container.lastChild) &&\n            !hasSingleChild\n        ) {\n            unwrapContents(container.lastChild);\n        }\n\n        startNode = startNode || this.dependencies.selection.getEditableSelection().anchorNode;\n\n        const shouldUnwrap = (node) =>\n            (isParagraphRelatedElement(node) || isListItemElement(node)) &&\n            !isEmptyBlock(block) &&\n            !isEmptyBlock(node) &&\n            (isContentEditable(node) ||\n                (!node.isConnected && !closestElement(node, \"[contenteditable]\"))) &&\n            !this.dependencies.split.isUnsplittable(node) &&\n            (node.nodeName === block.nodeName ||\n                (this.dependencies.baseContainer.isCandidateForBaseContainer(node) &&\n                    this.dependencies.baseContainer.isCandidateForBaseContainer(block)) ||\n                block.nodeName === \"PRE\" ||\n                (block.nodeName === \"DIV\" && this.dependencies.split.isUnsplittable(block))) &&\n            // If the selection anchorNode is the editable itself, the content\n            // should not be unwrapped.\n            !this.isEditionBoundary(selection.anchorNode);\n\n        // Empty block must contain a br element to allow cursor placement.\n        if (\n            container.lastElementChild &&\n            isBlock(container.lastElementChild) &&\n            !container.lastElementChild.hasChildNodes()\n        ) {\n            fillEmpty(container.lastElementChild);\n        }\n\n        // In case the html inserted is all contained in a single root <p> or <li>\n        // tag, we take the all content of the <p> or <li> and avoid inserting the\n        // <p> or <li>.\n        if (\n            container.childElementCount === 1 &&\n            (this.dependencies.baseContainer.isCandidateForBaseContainer(container.firstChild) ||\n                shouldUnwrap(container.firstChild))\n        ) {\n            const nodeToUnwrap = container.firstElementChild;\n            container.replaceChildren(...childNodes(nodeToUnwrap));\n        } else if (container.childElementCount > 1) {\n            const isSelectionAtStart =\n                firstLeaf(block) === selection.anchorNode && selection.anchorOffset === 0;\n            const isSelectionAtEnd =\n                lastLeaf(block) === selection.focusNode &&\n                selection.focusOffset === nodeSize(selection.focusNode);\n            // Grab the content of the first child block and isolate it.\n            if (shouldUnwrap(container.firstChild) && !isSelectionAtStart) {\n                // Unwrap the deepest nested first <li> element in the\n                // container to extract and paste the text content of the list.\n                if (isListItemElement(container.firstChild)) {\n                    const deepestBlock = closestBlock(firstLeaf(container.firstChild));\n                    this.dependencies.split.splitAroundUntil(deepestBlock, container.firstChild);\n                    container.firstElementChild.replaceChildren(...childNodes(deepestBlock));\n                }\n                containerFirstChild.replaceChildren(...childNodes(container.firstElementChild));\n                container.firstElementChild.remove();\n            }\n            // Grab the content of the last child block and isolate it.\n            if (shouldUnwrap(container.lastChild) && !isSelectionAtEnd) {\n                // Unwrap the deepest nested last <li> element in the container\n                // to extract and paste the text content of the list.\n                if (isListItemElement(container.lastChild)) {\n                    const deepestBlock = closestBlock(lastLeaf(container.lastChild));\n                    this.dependencies.split.splitAroundUntil(deepestBlock, container.lastChild);\n                    container.lastElementChild.replaceChildren(...childNodes(deepestBlock));\n                }\n                containerLastChild.replaceChildren(...childNodes(container.lastElementChild));\n                container.lastElementChild.remove();\n            }\n        }\n\n        const textNode = this.document.createTextNode(\"\");\n        if (startNode.nodeType === Node.ELEMENT_NODE) {\n            if (selection.anchorOffset === 0) {\n                if (isSelfClosingElement(startNode)) {\n                    startNode.parentNode.insertBefore(textNode, startNode);\n                } else {\n                    startNode.prepend(textNode);\n                }\n                startNode = textNode;\n                allInsertedNodes.push(textNode);\n            } else {\n                startNode = childNodes(startNode).at(selection.anchorOffset - 1);\n            }\n        }\n\n        // If we have isolated block content, first we split the current focus\n        // element if it's a block then we insert the content in the right places.\n        let currentNode = startNode;\n        const _insertAt = (reference, nodes, insertBefore) => {\n            for (const child of insertBefore ? nodes.reverse() : nodes) {\n                reference[insertBefore ? \"before\" : \"after\"](child);\n                reference = child;\n            }\n        };\n        const lastInsertedNodes = childNodes(containerLastChild);\n        if (containerLastChild.hasChildNodes()) {\n            const toInsert = childNodes(containerLastChild); // Prevent mutation\n            _insertAt(currentNode, [...toInsert], insertBefore);\n            currentNode = insertBefore ? toInsert[0] : currentNode;\n            toInsert[toInsert.length - 1];\n        }\n        const firstInsertedNodes = childNodes(containerFirstChild);\n        if (containerFirstChild.hasChildNodes()) {\n            const toInsert = childNodes(containerFirstChild); // Prevent mutation\n            _insertAt(currentNode, [...toInsert], insertBefore);\n            currentNode = toInsert[toInsert.length - 1];\n            insertBefore = false;\n        }\n        allInsertedNodes.push(...firstInsertedNodes);\n\n        // If all the Html have been isolated, We force a split of the parent element\n        // to have the need new line in the final result\n        if (!container.hasChildNodes()) {\n            if (this.dependencies.split.isUnsplittable(closestBlock(currentNode.nextSibling))) {\n                this.dependencies.lineBreak.insertLineBreakNode({\n                    targetNode: currentNode.nextSibling,\n                    targetOffset: 0,\n                });\n            } else {\n                // If we arrive here, the o_enter index should always be 0.\n                const parent = currentNode.nextSibling.parentElement;\n                const index = childNodes(parent).indexOf(currentNode.nextSibling);\n                this.dependencies.split.splitBlockNode({\n                    targetNode: parent,\n                    targetOffset: index,\n                });\n            }\n        }\n\n        let nodeToInsert;\n        let doesCurrentNodeAllowsP = allowsParagraphRelatedElements(currentNode);\n        const candidatesForRemoval = [];\n        const insertedNodes = childNodes(container);\n        while ((nodeToInsert = container.firstChild)) {\n            if (isBlock(nodeToInsert) && !doesCurrentNodeAllowsP) {\n                // Split blocks at the edges if inserting new blocks (preventing\n                // <p><p>text</p></p> or <li><li>text</li></li> scenarios).\n                while (\n                    !this.isEditionBoundary(currentNode.parentElement) &&\n                    (!allowsParagraphRelatedElements(currentNode.parentElement) ||\n                        (isListItemElement(currentNode.parentElement) &&\n                            !this.dependencies.split.isUnsplittable(nodeToInsert)))\n                ) {\n                    if (this.dependencies.split.isUnsplittable(currentNode.parentElement)) {\n                        // If we have to insert an unsplittable element, we cannot afford to\n                        // unwrap it we need to search for a more suitable spot to put it\n                        if (this.dependencies.split.isUnsplittable(nodeToInsert)) {\n                            currentNode = currentNode.parentElement;\n                            doesCurrentNodeAllowsP = allowsParagraphRelatedElements(currentNode);\n                            continue;\n                        } else {\n                            makeContentsInline(container);\n                            nodeToInsert = container.firstChild;\n                            break;\n                        }\n                    }\n                    let offset = childNodeIndex(currentNode);\n                    if (!insertBefore) {\n                        offset += 1;\n                    }\n                    if (offset) {\n                        const [left, right] = this.dependencies.split.splitElement(\n                            currentNode.parentElement,\n                            offset\n                        );\n                        currentNode = insertBefore ? right : left;\n                        const otherNode = insertBefore ? left : right;\n                        if (isBlock(otherNode)) {\n                            fillShrunkPhrasingParent(otherNode);\n                        }\n                        // After the content insertion, the right-part of a\n                        // split is evaluated for removal.\n                        candidatesForRemoval.push(right);\n                    } else {\n                        if (isBlock(currentNode)) {\n                            fillShrunkPhrasingParent(currentNode);\n                        }\n                        currentNode = currentNode.parentElement;\n                    }\n                    doesCurrentNodeAllowsP = allowsParagraphRelatedElements(currentNode);\n                }\n                if (\n                    isListItemElement(currentNode.parentElement) &&\n                    isBlock(nodeToInsert) &&\n                    this.dependencies.split.isUnsplittable(nodeToInsert)\n                ) {\n                    const br = document.createElement(\"br\");\n                    currentNode[\n                        isEmptyBlock(currentNode) || !isTangible(currentNode) ? \"before\" : \"after\"\n                    ](br);\n                }\n            }\n            // Ensure that all adjacent paragraph elements are converted to\n            // <li> when inserting in a list.\n            const block = closestBlock(currentNode);\n            for (const processor of this.getResource(\"node_to_insert_processors\")) {\n                nodeToInsert = processor({ nodeToInsert, container: block });\n            }\n            if (insertBefore) {\n                currentNode.before(nodeToInsert);\n                insertBefore = false;\n            } else {\n                currentNode.after(nodeToInsert);\n            }\n            allInsertedNodes.push(nodeToInsert);\n            if (currentNode.tagName !== \"BR\" && isShrunkBlock(currentNode)) {\n                currentNode.remove();\n            }\n            currentNode = nodeToInsert;\n        }\n        // Remove the empty text node created earlier\n        textNode.remove();\n        allInsertedNodes.push(...lastInsertedNodes);\n        this.getResource(\"after_insert_handlers\").forEach((handler) => handler(allInsertedNodes));\n        let insertedNodesParents = getConnectedParents(allInsertedNodes);\n        for (const parent of insertedNodesParents) {\n            if (\n                !this.config.allowInlineAtRoot &&\n                this.isEditionBoundary(parent) &&\n                allowsParagraphRelatedElements(parent)\n            ) {\n                // Ensure that edition boundaries do not have inline content.\n                wrapInlinesInBlocks(parent, {\n                    baseContainerNodeName: this.dependencies.baseContainer.getDefaultNodeName(),\n                });\n            }\n        }\n        insertedNodesParents = getConnectedParents(allInsertedNodes);\n        for (const parent of insertedNodesParents) {\n            if (\n                !isProtecting(parent) &&\n                !(isProtected(parent) && !isUnprotecting(parent)) &&\n                parent.isContentEditable\n            ) {\n                cleanTrailingBR(parent);\n            }\n        }\n        for (const candidateForRemoval of candidatesForRemoval) {\n            if (\n                candidateForRemoval.isConnected &&\n                (isParagraphRelatedElement(candidateForRemoval) ||\n                    isListItemElement(candidateForRemoval)) &&\n                candidateForRemoval.parentElement.isContentEditable &&\n                isEmptyBlock(candidateForRemoval)\n            ) {\n                candidateForRemoval.remove();\n            }\n        }\n        for (const insertedNode of allInsertedNodes.reverse()) {\n            if (insertedNode.isConnected) {\n                currentNode = insertedNode;\n                break;\n            }\n        }\n        let lastPosition =\n            isParagraphRelatedElement(currentNode) ||\n            isListItemElement(currentNode) ||\n            isListElement(currentNode)\n                ? rightPos(lastLeaf(currentNode))\n                : rightPos(currentNode);\n        lastPosition = normalizeCursorPosition(lastPosition[0], lastPosition[1], \"right\");\n\n        if (!this.config.allowInlineAtRoot && this.isEditionBoundary(lastPosition[0])) {\n            // Correct the position if it happens to be in the editable root.\n            lastPosition = getDeepestPosition(...lastPosition);\n        }\n        this.dependencies.selection.setSelection(\n            { anchorNode: lastPosition[0], anchorOffset: lastPosition[1] },\n            { normalize: false }\n        );\n        return firstInsertedNodes.concat(insertedNodes).concat(lastInsertedNodes);\n    }\n\n    isEditionBoundary(node) {\n        if (!node) {\n            return false;\n        }\n        if (node === this.editable) {\n            return true;\n        }\n        return isContentEditableAncestor(node);\n    }\n\n    /**\n     * @param {HTMLElement} source\n     * @param {HTMLElement} target\n     */\n    copyAttributes(source, target) {\n        if (source?.nodeType !== Node.ELEMENT_NODE || target?.nodeType !== Node.ELEMENT_NODE) {\n            return;\n        }\n        const ignoredAttrs = new Set(this.getResource(\"system_attributes\"));\n        const ignoredClasses = new Set(this.getResource(\"system_classes\"));\n        for (const attr of source.attributes) {\n            if (ignoredAttrs.has(attr.name)) {\n                continue;\n            }\n            if (attr.name !== \"class\" || ignoredClasses.size === 0) {\n                target.setAttribute(attr.name, attr.value);\n            } else {\n                const classes = [...source.classList];\n                for (const className of classes) {\n                    if (!ignoredClasses.has(className)) {\n                        target.classList.add(className);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Basic method to change an element tagName.\n     * It is a technical function which only modifies a tag and its attributes.\n     * It does not modify descendants nor handle the cursor.\n     * @see setBlock for the more thorough command.\n     *\n     * @param {HTMLElement} el\n     * @param {string} newTagName\n     */\n    setTagName(el, newTagName) {\n        const document = el.ownerDocument;\n        if (el.tagName === newTagName) {\n            return el;\n        }\n        const newEl = document.createElement(newTagName);\n        const content = childNodes(el);\n        if (isListItemElement(el)) {\n            el.append(newEl);\n            newEl.replaceChildren(...content);\n        } else {\n            if (el.parentElement) {\n                el.before(newEl);\n            }\n            this.copyAttributes(el, newEl);\n            newEl.replaceChildren(...content);\n            el.remove();\n        }\n        return newEl;\n    }\n\n    /**\n     * Remove system-specific classes, attributes, and style properties from a\n     * fragment or an element.\n     *\n     * @param {DocumentFragment|HTMLElement} root\n     */\n    removeSystemProperties(root) {\n        const clean = (element) => {\n            removeClass(element, ...this.systemClasses);\n            this.systemAttributes.forEach((attr) => element.removeAttribute(attr));\n            removeStyle(element, ...this.systemStyleProperties);\n        };\n        if (root.matches?.(this.systemPropertiesSelector)) {\n            clean(root);\n        }\n        for (const element of root.querySelectorAll(this.systemPropertiesSelector)) {\n            clean(element);\n        }\n    }\n\n    // --------------------------------------------------------------------------\n    // commands\n    // --------------------------------------------------------------------------\n\n    insertFontAwesome({ faClass = \"fa fa-star\" } = {}) {\n        const fontAwesomeNode = document.createElement(\"i\");\n        fontAwesomeNode.className = faClass;\n        this.insert(fontAwesomeNode);\n        this.dependencies.history.addStep();\n        const [anchorNode, anchorOffset] = rightPos(fontAwesomeNode);\n        this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n    }\n\n    /**\n     * Determines if a block element can be safely retagged.\n     *\n     * Certain blocks (like 'o_editable') should not be retagged because doing so\n     * will recreate the block, potentially causing issues. This function checks\n     * if retagging a block is safe.\n     *\n     * @param {HTMLElement} block\n     * @returns {boolean}\n     */\n    isRetaggingSafe(block) {\n        return !(\n            (isParagraphRelatedElement(block) ||\n                isListItemElement(block) ||\n                isPhrasingContent(block)) &&\n            this.getResource(\"unremovable_node_predicates\").some((predicate) => predicate(block))\n        );\n    }\n\n    getBlocksToSet() {\n        const targetedBlocks = [...this.dependencies.selection.getTargetedBlocks()];\n        return targetedBlocks.filter(\n            (block) =>\n                this.isRetaggingSafe(block) &&\n                !descendants(block).some((descendant) => targetedBlocks.includes(descendant)) &&\n                block.isContentEditable\n        );\n    }\n\n    canSetBlock() {\n        return this.getBlocksToSet().length > 0;\n    }\n\n    /**\n     * @param {Object} param0\n     * @param {string} param0.tagName\n     * @param {string} [param0.extraClass]\n     */\n    setBlock({ tagName, extraClass = \"\" }) {\n        let newCandidate = this.document.createElement(tagName.toUpperCase());\n        if (extraClass) {\n            newCandidate.classList.add(extraClass);\n        }\n        if (this.dependencies.baseContainer.isCandidateForBaseContainer(newCandidate)) {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer(\n                newCandidate.nodeName\n            );\n            this.copyAttributes(newCandidate, baseContainer);\n            newCandidate = baseContainer;\n        }\n        const cursors = this.dependencies.selection.preserveSelection();\n        const newEls = [];\n        for (const block of this.getBlocksToSet()) {\n            if (\n                isParagraphRelatedElement(block) ||\n                isListItemElement(block) ||\n                isPhrasingContent(block) ||\n                block.nodeName === \"BLOCKQUOTE\"\n            ) {\n                if (newCandidate.matches(baseContainerGlobalSelector) && isListItemElement(block)) {\n                    continue;\n                }\n                this.dispatchTo(\"before_set_tag_handlers\", block, tagName, cursors);\n                const newEl = this.setTagName(block, tagName);\n                cursors.remapNode(block, newEl);\n                // We want to be able to edit the case `<h2 class=\"h3\">`\n                // but in that case, we want to display \"Header 2\" and\n                // not \"Header 3\" as it is more important to display\n                // the semantic tag being used (especially for h1 ones).\n                // This is why those are not in `TEXT_STYLE_CLASSES`.\n                const headingClasses = [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"];\n                removeClass(newEl, ...FONT_SIZE_CLASSES, ...TEXT_STYLE_CLASSES, ...headingClasses);\n                delete newEl.style.fontSize;\n                if (extraClass) {\n                    newEl.classList.add(extraClass);\n                }\n                newEls.push(newEl);\n            } else {\n                // eg do not change a <div> into a h1: insert the h1\n                // into it instead.\n                newCandidate.append(...childNodes(block));\n                block.append(newCandidate);\n                cursors.remapNode(block, newCandidate);\n            }\n        }\n        cursors.restore();\n        this.dependencies.history.addStep();\n    }\n\n    removeEmptyClassAndStyleAttributes(root) {\n        for (const node of [root, ...descendants(root)]) {\n            if (node.classList && !node.classList.length) {\n                node.removeAttribute(\"class\");\n            }\n            if (node.style && !node.style.length) {\n                node.removeAttribute(\"style\");\n            }\n        }\n    }\n}\n", "import {\n    htmlEditorVersions,\n    stripVersion,\n    VERSION_SELECTOR,\n} from \"@html_editor/html_migrations/html_migrations_utils\";\nimport { Plugin } from \"@html_editor/plugin\";\n\nexport class EditorVersionPlugin extends Plugin {\n    static id = \"editorVersion\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        normalize_handlers: this.normalize.bind(this),\n    };\n\n    normalize(element) {\n        if (element.matches(VERSION_SELECTOR) && element !== this.editable) {\n            delete element.dataset.oeVersion;\n        }\n        stripVersion(element);\n    }\n\n    cleanForSave({ root }) {\n        const VERSIONS = htmlEditorVersions();\n        const firstChild = root.firstElementChild;\n        const version = VERSIONS.at(-1);\n        if (firstChild && version) {\n            firstChild.dataset.oeVersion = version;\n        }\n    }\n}\n", "import { prepareUpdate } from \"@html_editor/utils/dom_state\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { callbacksForCursorUpdate } from \"@html_editor/utils/selection\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Plugin } from \"../plugin\";\nimport { closestBlock, isBlock } from \"../utils/blocks\";\nimport { cleanTextNode, fillEmpty, removeClass, splitTextNode, unwrapContents } from \"../utils/dom\";\nimport {\n    areSimilarElements,\n    isContentEditable,\n    isElement,\n    isEmptyBlock,\n    isEmptyTextNode,\n    isSelfClosingElement,\n    isTextNode,\n    isVisibleTextNode,\n    isZwnbsp,\n    isZWS,\n    previousLeaf,\n} from \"../utils/dom_info\";\nimport { isFakeLineBreak } from \"../utils/dom_state\";\nimport {\n    childNodes,\n    closestElement,\n    descendants,\n    findFurthest,\n    selectElements,\n} from \"../utils/dom_traversal\";\nimport { formatsSpecs, FORMATTABLE_TAGS } from \"../utils/formatting\";\nimport { boundariesIn, boundariesOut, DIRECTIONS, leftPos, rightPos } from \"../utils/position\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nconst allWhitespaceRegex = /^[\\s\\u200b]*$/;\n\nfunction isFormatted(formatPlugin, format) {\n    return (sel, nodes) => formatPlugin.isSelectionFormat(format, nodes);\n}\n\n/**\n * @typedef {Object} FormatShared\n * @property { FormatPlugin['isSelectionFormat'] } isSelectionFormat\n * @property { FormatPlugin['insertAndSelectZws'] } insertAndSelectZws\n * @property { FormatPlugin['mergeAdjacentInlines'] } mergeAdjacentInlines\n * @property { FormatPlugin['formatSelection'] } formatSelection\n */\n\n/**\n * @typedef {((formatName: string, options: {\n *      formatProps: object,\n *      applyStyle: boolean,\n * }) => void | boolean)[]} format_selection_handlers\n * @typedef {(() => void)[]} remove_all_formats_handlers\n *\n * @typedef {((className: string) => boolean)[]} format_class_predicates\n * @typedef {((node: Node) => boolean)[]} has_format_predicates\n */\n\nexport class FormatPlugin extends Plugin {\n    static id = \"format\";\n    static dependencies = [\"selection\", \"history\", \"input\", \"split\"];\n    // TODO ABD: refactor to handle Knowledge comments inside this plugin without sharing mergeAdjacentInlines.\n    static shared = [\n        \"isSelectionFormat\",\n        \"insertAndSelectZws\",\n        \"mergeAdjacentInlines\",\n        \"formatSelection\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"formatBold\",\n                description: _t(\"Toggle bold\"),\n                icon: \"fa-bold\",\n                run: this.formatSelection.bind(this, \"bold\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"formatItalic\",\n                description: _t(\"Toggle italic\"),\n                icon: \"fa-italic\",\n                run: this.formatSelection.bind(this, \"italic\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"formatUnderline\",\n                description: _t(\"Toggle underline\"),\n                icon: \"fa-underline\",\n                run: this.formatSelection.bind(this, \"underline\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"formatStrikethrough\",\n                description: _t(\"Toggle strikethrough\"),\n                icon: \"fa-strikethrough\",\n                run: this.formatSelection.bind(this, \"strikeThrough\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"formatFontSize\",\n                run: ({ size }) =>\n                    this.formatSelection(\"fontSize\", {\n                        applyStyle: true,\n                        formatProps: { size },\n                    }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"formatFontSizeClassName\",\n                run: ({ className }) =>\n                    this.formatSelection(\"setFontSizeClassName\", {\n                        applyStyle: true,\n                        formatProps: { className },\n                    }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"removeFormat\",\n                description: (sel, nodes) =>\n                    nodes && this.hasAnyFormat(nodes)\n                        ? _t(\"Remove Format\")\n                        : _t(\"Selection has no format\"),\n                icon: \"fa-eraser\",\n                run: this.removeAllFormats.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        shortcuts: [\n            { hotkey: \"control+b\", commandId: \"formatBold\" },\n            { hotkey: \"control+i\", commandId: \"formatItalic\" },\n            { hotkey: \"control+u\", commandId: \"formatUnderline\" },\n            { hotkey: \"control+5\", commandId: \"formatStrikethrough\" },\n            { hotkey: \"control+space\", commandId: \"removeFormat\" },\n        ],\n        toolbar_groups: withSequence(20, { id: \"decoration\" }),\n        toolbar_items: [\n            {\n                id: \"bold\",\n                groupId: \"decoration\",\n                namespaces: [\"compact\", \"expanded\"],\n                commandId: \"formatBold\",\n                isActive: isFormatted(this, \"bold\"),\n            },\n            {\n                id: \"italic\",\n                groupId: \"decoration\",\n                namespaces: [\"compact\", \"expanded\"],\n                commandId: \"formatItalic\",\n                isActive: isFormatted(this, \"italic\"),\n            },\n            {\n                id: \"underline\",\n                groupId: \"decoration\",\n                namespaces: [\"compact\", \"expanded\"],\n                commandId: \"formatUnderline\",\n                isActive: isFormatted(this, \"underline\"),\n            },\n            {\n                id: \"strikethrough\",\n                groupId: \"decoration\",\n                commandId: \"formatStrikethrough\",\n                isActive: isFormatted(this, \"strikeThrough\"),\n            },\n            withSequence(20, {\n                id: \"remove_format\",\n                groupId: \"decoration\",\n                commandId: \"removeFormat\",\n                isDisabled: (sel, nodes) => !this.hasAnyFormat(nodes),\n            }),\n        ],\n        /** Handlers */\n        beforeinput_handlers: withSequence(20, this.onBeforeInput.bind(this)),\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        normalize_handlers: this.normalize.bind(this),\n        selectionchange_handlers: this.removeEmptyInlineElement.bind(this),\n        before_set_tag_handlers: this.removeFontSizeFormat.bind(this),\n        before_insert_processors: this.unwrapEmptyFormat.bind(this),\n\n        intangible_char_for_keyboard_navigation_predicates: (_, char) => char === \"\\u200b\",\n    };\n\n    /**\n     * @param {string[]} formats\n     * @param {Node[]} targetedNodes\n     */\n    removeFormats(formats, targetedNodes) {\n        for (const format of formats) {\n            if (\n                !formatsSpecs[format].removeStyle ||\n                !this.hasSelectionFormat(format, targetedNodes)\n            ) {\n                continue;\n            }\n            this.formatSelection(format, { applyStyle: false, removeFormat: true });\n        }\n    }\n\n    unwrapEmptyFormat(insertedNode) {\n        const anchorNode = this.dependencies.selection.getEditableSelection().anchorNode;\n        if (!allWhitespaceRegex.test(insertedNode.textContent)) {\n            return insertedNode;\n        }\n        const emptyZWS = closestElement(anchorNode, \"[data-oe-zws-empty-inline]\");\n        if (\n            !emptyZWS ||\n            !emptyZWS.parentElement.isContentEditable ||\n            this.getResource(\"unremovable_node_predicates\").some((p) => p(emptyZWS))\n        ) {\n            return insertedNode;\n        }\n        const cursors = this.dependencies.selection.preserveSelection();\n        cursors.update(callbacksForCursorUpdate.remove(emptyZWS));\n        emptyZWS.remove();\n        cursors.restore();\n        return insertedNode;\n    }\n\n    removeAllFormats() {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        this.removeFormats(Object.keys(formatsSpecs), targetedNodes);\n        this.dispatchTo(\"remove_all_formats_handlers\");\n        this.dependencies.history.addStep();\n    }\n\n    removeFontSizeFormat(el) {\n        this.removeFormats([\"fontSize\", \"setFontSizeClassName\"], [el, ...descendants(el)]);\n    }\n\n    /**\n     * Return true if the current selection on the editable contains a formated\n     * node\n     *\n     * @param {String} format 'bold'|'italic'|'underline'|'strikeThrough'|'switchDirection'\n     * @param {Node[]} [targetedNodes]\n     * @returns {boolean}\n     */\n    hasSelectionFormat(format, targetedNodes = this.dependencies.selection.getTargetedNodes()) {\n        const targetedTextNodes = targetedNodes.filter(isTextNode);\n        const isFormatted = formatsSpecs[format].isFormatted;\n        return targetedTextNodes.some((n) => isFormatted(n, { editable: this.editable }));\n    }\n    /**\n     * Return true if the current selection on the editable appears as the given\n     * format. The selection is considered to appear as that format if every\n     * text node in it appears as that format.\n     *\n     * @param {String} format 'bold'|'italic'|'underline'|'strikeThrough'|'switchDirection'\n     * @param {Node[]} [targetedNodes]\n     * @returns {boolean}\n     */\n    isSelectionFormat(format, targetedNodes = this.dependencies.selection.getTargetedNodes()) {\n        const targetedTextNodes = targetedNodes.filter(isTextNode);\n        const isFormatted = formatsSpecs[format].isFormatted;\n        return (\n            targetedTextNodes.length &&\n            targetedTextNodes.every(\n                (node) =>\n                    isZwnbsp(node) ||\n                    isEmptyTextNode(node) ||\n                    isFormatted(node, { editable: this.editable })\n            )\n        );\n    }\n\n    hasAnyFormat(targetedNodes) {\n        for (const format of Object.keys(formatsSpecs)) {\n            if (\n                formatsSpecs[format].removeStyle &&\n                this.hasSelectionFormat(format, targetedNodes)\n            ) {\n                return true;\n            }\n        }\n        return targetedNodes.some((node) =>\n            this.getResource(\"has_format_predicates\").some((predicate) => predicate(node))\n        );\n    }\n\n    formatSelection(formatName, options) {\n        this.dispatchTo(\"format_selection_handlers\", formatName, options);\n        if (this._formatSelection(formatName, options) && !options?.removeFormat) {\n            this.dependencies.history.addStep();\n        }\n    }\n\n    // @todo phoenix: refactor this method.\n    _formatSelection(formatName, { applyStyle, formatProps } = {}) {\n        this.dependencies.selection.selectAroundNonEditable();\n        // note: does it work if selection is in opposite direction?\n        const selection = this.dependencies.split.splitSelection();\n        if (typeof applyStyle === \"undefined\") {\n            applyStyle = !this.isSelectionFormat(formatName);\n        }\n\n        let zws;\n        if (selection.isCollapsed) {\n            if (isTextNode(selection.anchorNode) && selection.anchorNode.textContent === \"\\u200b\") {\n                zws = selection.anchorNode;\n                this.dependencies.selection.setSelection({\n                    anchorNode: zws,\n                    anchorOffset: 0,\n                    focusNode: zws,\n                    focusOffset: 1,\n                });\n            } else {\n                zws = this.insertAndSelectZws();\n            }\n        }\n\n        const selectedTextNodes = /** @type { Text[] } **/ (\n            this.dependencies.selection\n                .getTargetedNodes()\n                .filter(\n                    (n) =>\n                        this.dependencies.selection.areNodeContentsFullySelected(n) &&\n                        ((isTextNode(n) && (isVisibleTextNode(n) || isZWS(n))) ||\n                            (n.nodeName === \"BR\" &&\n                                (isFakeLineBreak(n) ||\n                                    previousLeaf(n, closestBlock(n))?.nodeName === \"BR\"))) &&\n                        isContentEditable(n)\n                )\n        );\n        const unformattedTextNodes = selectedTextNodes.filter((n) => {\n            const listItem = closestElement(n, \"li\");\n            if (listItem && this.dependencies.selection.areNodeContentsFullySelected(listItem)) {\n                const hasFontSizeStyle =\n                    formatName === \"setFontSizeClassName\"\n                        ? listItem.classList.contains(formatProps?.className)\n                        : listItem.style.fontSize;\n                return !hasFontSizeStyle;\n            }\n            return true;\n        });\n\n        const tagetedFieldNodes = new Set(\n            this.dependencies.selection\n                .getTargetedNodes()\n                .map((n) => closestElement(n, \"*[t-field],*[t-out],*[t-esc]\"))\n                .filter(Boolean)\n        );\n        const formatSpec = formatsSpecs[formatName];\n        for (const node of unformattedTextNodes) {\n            const inlineAncestors = [];\n            /** @type { Node } */\n            let currentNode = node;\n            let parentNode = node.parentElement;\n\n            // Remove the format on all inline ancestors until a block or an element\n            // with a class that is not indicated as splittable.\n            const isClassListSplittable = (classList) =>\n                [...classList].every((className) =>\n                    this.getResource(\"format_class_predicates\").some((cb) => cb(className))\n                );\n\n            while (\n                parentNode &&\n                !isBlock(parentNode) &&\n                !this.dependencies.split.isUnsplittable(parentNode) &&\n                (parentNode.classList.length === 0 || isClassListSplittable(parentNode.classList))\n            ) {\n                const isUselessZws =\n                    parentNode.tagName === \"SPAN\" &&\n                    parentNode.hasAttribute(\"data-oe-zws-empty-inline\") &&\n                    parentNode.getAttributeNames().length === 1;\n\n                if (isUselessZws) {\n                    unwrapContents(parentNode);\n                } else {\n                    const newLastAncestorInlineFormat = this.dependencies.split.splitAroundUntil(\n                        currentNode,\n                        parentNode\n                    );\n                    removeFormat(newLastAncestorInlineFormat, formatSpec);\n                    if ([\"setFontSizeClassName\", \"fontSize\"].includes(formatName) && applyStyle) {\n                        removeClass(newLastAncestorInlineFormat, \"o_default_font_size\");\n                    }\n                    if (newLastAncestorInlineFormat.isConnected) {\n                        inlineAncestors.push(newLastAncestorInlineFormat);\n                        currentNode = newLastAncestorInlineFormat;\n                    }\n                }\n\n                parentNode = currentNode.parentElement;\n            }\n\n            const firstBlockOrClassHasFormat = formatSpec.isFormatted(parentNode, formatProps);\n            if (firstBlockOrClassHasFormat && !applyStyle) {\n                const isParentNodeBlockAndCompletelySelected =\n                    isBlock(parentNode) &&\n                    this.dependencies.selection.areNodeContentsFullySelected(parentNode);\n                if (\n                    isParentNodeBlockAndCompletelySelected &&\n                    formatName === \"setFontSizeClassName\"\n                ) {\n                    for (const node of [parentNode, ...descendants(parentNode).filter(isElement)]) {\n                        removeFormat(node, formatSpec);\n                    }\n                } else {\n                    formatSpec.addNeutralStyle &&\n                        formatSpec.addNeutralStyle(getOrCreateSpan(node, inlineAncestors));\n                }\n            } else if (\n                (!firstBlockOrClassHasFormat || parentNode.nodeName === \"LI\") &&\n                applyStyle\n            ) {\n                const tag = formatSpec.tagName && this.document.createElement(formatSpec.tagName);\n                if (tag) {\n                    node.after(tag);\n                    tag.append(node);\n\n                    if (!formatSpec.isFormatted(tag, formatProps)) {\n                        tag.after(node);\n                        tag.remove();\n                        formatSpec.addStyle(getOrCreateSpan(node, inlineAncestors), formatProps);\n                    }\n                } else if (formatName !== \"fontSize\" || formatProps.size !== undefined) {\n                    formatSpec.addStyle(getOrCreateSpan(node, inlineAncestors), formatProps);\n                }\n            }\n        }\n\n        for (const targetedFieldNode of tagetedFieldNodes) {\n            if (applyStyle) {\n                formatSpec.addStyle(targetedFieldNode, formatProps);\n            } else {\n                formatSpec.removeStyle(targetedFieldNode);\n            }\n        }\n\n        if (zws) {\n            const siblings = [...zws.parentElement.childNodes];\n            if (\n                !isBlock(zws.parentElement) &&\n                unformattedTextNodes.includes(siblings[0]) &&\n                unformattedTextNodes.includes(siblings[siblings.length - 1])\n            ) {\n                zws.parentElement.setAttribute(\"data-oe-zws-empty-inline\", \"\");\n            } else {\n                const span = this.document.createElement(\"span\");\n                span.setAttribute(\"data-oe-zws-empty-inline\", \"\");\n                zws.before(span);\n                span.append(zws);\n            }\n        }\n\n        if (\n            unformattedTextNodes.length === 1 &&\n            unformattedTextNodes[0] &&\n            unformattedTextNodes[0].textContent === \"\\u200B\"\n        ) {\n            this.dependencies.selection.setCursorStart(unformattedTextNodes[0]);\n        } else if (selectedTextNodes.length) {\n            const firstNode = selectedTextNodes[0];\n            const lastNode = selectedTextNodes[selectedTextNodes.length - 1];\n            let newSelection;\n            if (selection.direction === DIRECTIONS.RIGHT) {\n                newSelection = {\n                    anchorNode: firstNode,\n                    anchorOffset: 0,\n                    focusNode: lastNode,\n                    focusOffset: lastNode.length,\n                };\n            } else {\n                newSelection = {\n                    anchorNode: lastNode,\n                    anchorOffset: lastNode.length,\n                    focusNode: firstNode,\n                    focusOffset: 0,\n                };\n            }\n            this.dependencies.selection.setSelection(newSelection, { normalize: false });\n            return true;\n        }\n        if (tagetedFieldNodes.size > 0) {\n            return true;\n        }\n    }\n\n    normalize(root) {\n        for (const el of selectElements(root, \"[data-oe-zws-empty-inline]\")) {\n            if (!allWhitespaceRegex.test(el.textContent)) {\n                // The element has some meaningful text. Remove the ZWS in it.\n                delete el.dataset.oeZwsEmptyInline;\n                this.cleanZWS(el);\n                if (\n                    el.tagName === \"SPAN\" &&\n                    el.getAttributeNames().length === 0 &&\n                    el.classList.length === 0\n                ) {\n                    // Useless span, unwrap it.\n                    unwrapContents(el);\n                }\n            }\n        }\n        this.mergeAdjacentInlines(root);\n    }\n\n    cleanForSave({ root, preserveSelection = false } = {}) {\n        for (const element of root.querySelectorAll(\"[data-oe-zws-empty-inline]\")) {\n            let currentElement = element.parentElement;\n            this.cleanElement(element, { preserveSelection });\n            while (\n                currentElement &&\n                !isBlock(currentElement) &&\n                !currentElement.childNodes.length\n            ) {\n                const parentElement = currentElement.parentElement;\n                currentElement.remove();\n                currentElement = parentElement;\n            }\n            if (currentElement && isBlock(currentElement)) {\n                fillEmpty(currentElement);\n            }\n        }\n        this.mergeAdjacentInlines(root, { preserveSelection });\n    }\n\n    removeEmptyInlineElement(selectionData) {\n        const { anchorNode } = selectionData.editableSelection;\n        const blockEl = closestBlock(anchorNode);\n        const inlineElement = findFurthest(\n            closestElement(anchorNode),\n            blockEl,\n            (e) => !isBlock(e) && e.textContent === \"\\u200b\"\n        );\n        if (\n            this.lastEmptyInlineElement?.isConnected &&\n            this.lastEmptyInlineElement !== inlineElement\n        ) {\n            // Remove last empty inline element.\n            this.cleanElement(this.lastEmptyInlineElement, { preserveSelection: true });\n        }\n        // Skip if current block is empty.\n        if (inlineElement && !isEmptyBlock(blockEl)) {\n            this.lastEmptyInlineElement = inlineElement;\n        } else {\n            this.lastEmptyInlineElement = null;\n        }\n    }\n\n    cleanElement(element, { preserveSelection }) {\n        delete element.dataset.oeZwsEmptyInline;\n        if (!allWhitespaceRegex.test(element.textContent)) {\n            // The element has some meaningful text. Remove the ZWS in it.\n            this.cleanZWS(element, { preserveSelection });\n            return;\n        }\n        if (this.getResource(\"unremovable_node_predicates\").some((p) => p(element))) {\n            return;\n        }\n        if (\n            ![...element.classList].every((c) =>\n                this.getResource(\"format_class_predicates\").some((p) => p(c))\n            )\n        ) {\n            // Original comment from web_editor:\n            // We only remove the empty element if it has no class, to ensure we\n            // don't break visual styles (in that case, its ZWS was kept to\n            // ensure the cursor can be placed in it).\n            return;\n        }\n        const restore = prepareUpdate(...leftPos(element), ...rightPos(element));\n        element.remove();\n        restore();\n    }\n\n    cleanZWS(element, { preserveSelection = true } = {}) {\n        const textNodes = descendants(element).filter(isTextNode);\n        const cursors = preserveSelection ? this.dependencies.selection.preserveSelection() : null;\n        for (const node of textNodes) {\n            cleanTextNode(node, \"\\u200B\", cursors);\n        }\n        cursors?.restore();\n    }\n\n    insertText(selection, content) {\n        if (selection.anchorNode.nodeType === Node.TEXT_NODE) {\n            selection = this.dependencies.selection.setSelection(\n                {\n                    anchorNode: selection.anchorNode.parentElement,\n                    anchorOffset: splitTextNode(selection.anchorNode, selection.anchorOffset),\n                },\n                { normalize: false }\n            );\n        }\n\n        const txt = this.document.createTextNode(content || \"#\");\n        const restore = prepareUpdate(selection.anchorNode, selection.anchorOffset);\n        selection.anchorNode.insertBefore(\n            txt,\n            selection.anchorNode.childNodes[selection.anchorOffset]\n        );\n        restore();\n        const [anchorNode, anchorOffset, focusNode, focusOffset] = boundariesOut(txt);\n        this.dependencies.selection.setSelection(\n            { anchorNode, anchorOffset, focusNode, focusOffset },\n            { normalize: false }\n        );\n        return txt;\n    }\n\n    /**\n     * Use the actual selection (assumed to be collapsed) and insert a\n     * zero-width space at its anchor point. Then, select that zero-width\n     * space.\n     *\n     * @returns {Node} the inserted zero-width space\n     */\n    insertAndSelectZws() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const zws = this.insertText(selection, \"\\u200B\");\n        splitTextNode(zws, selection.anchorOffset);\n        return zws;\n    }\n\n    onBeforeInput(ev) {\n        if (\n            ev.inputType.startsWith(\"format\") &&\n            !isHtmlContentSupported(this.dependencies.selection.getEditableSelection())\n        ) {\n            ev.preventDefault();\n        }\n        if (ev.inputType === \"insertText\") {\n            const selection = this.dependencies.selection.getEditableSelection();\n            if (!selection.isCollapsed) {\n                return;\n            }\n            const element = closestElement(selection.anchorNode);\n            if (element.hasAttribute(\"data-oe-zws-empty-inline\")) {\n                // Select its ZWS content to make sure the text will be\n                // inserted inside the element, and not before (outside) it.\n                // This addresses an undesired behavior of the\n                // contenteditable.\n                const [anchorNode, anchorOffset, focusNode, focusOffset] = boundariesIn(element);\n                this.dependencies.selection.setSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                });\n            }\n        }\n    }\n\n    /**\n     * @param {Node} root\n     * @param {Object} [options]\n     * @param {boolean} [options.preserveSelection=true]\n     */\n    mergeAdjacentInlines(root, { preserveSelection = true } = {}) {\n        let selectionToRestore = null;\n        for (const node of [root, ...descendants(root)].filter(isElement)) {\n            if (this.shouldBeMergedWithPreviousSibling(node)) {\n                if (preserveSelection) {\n                    selectionToRestore ??= this.dependencies.selection.preserveSelection();\n                    selectionToRestore.update(callbacksForCursorUpdate.merge(node));\n                }\n                node.previousSibling.append(...childNodes(node));\n                node.remove();\n            }\n        }\n        selectionToRestore?.restore();\n    }\n\n    shouldBeMergedWithPreviousSibling(node) {\n        const isMergeable = (node) =>\n            FORMATTABLE_TAGS.includes(node.nodeName) &&\n            !this.getResource(\"unsplittable_node_predicates\").some((predicate) => predicate(node));\n        return (\n            !isSelfClosingElement(node) &&\n            areSimilarElements(node, node.previousSibling) &&\n            isMergeable(node)\n        );\n    }\n}\n\nfunction getOrCreateSpan(node, ancestors) {\n    const document = node.ownerDocument;\n    const span = ancestors.find((element) => element.tagName === \"SPAN\" && element.isConnected);\n    const lastInlineAncestor = ancestors.findLast(\n        (element) => !isBlock(element) && element.isConnected\n    );\n    if (span) {\n        return span;\n    } else {\n        const span = document.createElement(\"span\");\n        // Apply font span above current inline top ancestor so that\n        // the font style applies to the other style tags as well.\n        if (lastInlineAncestor) {\n            lastInlineAncestor.after(span);\n            span.append(lastInlineAncestor);\n        } else {\n            node.after(span);\n            span.append(node);\n        }\n        return span;\n    }\n}\nfunction removeFormat(node, formatSpec) {\n    const document = node.ownerDocument;\n    node = closestElement(node);\n    if (formatSpec.hasStyle(node)) {\n        formatSpec.removeStyle(node);\n        if ([\"SPAN\", \"FONT\"].includes(node.tagName) && !node.getAttributeNames().length) {\n            return unwrapContents(node);\n        }\n    }\n\n    if (formatSpec.isTag && formatSpec.isTag(node)) {\n        const attributesNames = node\n            .getAttributeNames()\n            .filter((name) => name !== \"data-oe-zws-empty-inline\");\n        if (attributesNames.length) {\n            // Change tag name\n            const newNode = document.createElement(\"span\");\n            while (node.firstChild) {\n                newNode.appendChild(node.firstChild);\n            }\n            for (let index = node.attributes.length - 1; index >= 0; --index) {\n                newNode.attributes.setNamedItem(node.attributes[index].cloneNode());\n            }\n            node.parentNode.replaceChild(newNode, node);\n        } else {\n            unwrapContents(node);\n        }\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Plugin } from \"../plugin\";\nimport { childNodes, descendants, getCommonAncestor } from \"../utils/dom_traversal\";\nimport { hasTouch } from \"@web/core/browser/feature_detection\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { toggleClass } from \"@html_editor/utils/dom\";\nimport { omit, pick } from \"@web/core/utils/objects\";\nimport { trackOccurrences, trackOccurrencesPair } from \"../utils/tracking\";\n\n/**\n * @typedef { import(\"./selection_plugin\").EditorSelection } EditorSelection\n *\n * @typedef { Object } SerializedSelection\n * @property { string } anchorNodeId\n * @property { number } anchorOffset\n * @property { string } focusNodeId\n * @property { number } focusOffset\n *\n * @typedef { Object } SerializedNode\n * @property { number } nodeType\n * @property { string } nodeId\n * @property { string } textValue\n * @property { string } tagName\n * @property { SerializedNode[] } children\n * @property { Record<string, string> } attributes\n *\n * @typedef { Object } HistoryStep\n * @property { string } id\n * @property {\"original\"|\"undo\"|\"redo\"|\"restore\"} type\n * @property { SerializedSelection } selection\n * @property { HistoryMutation[] } mutations\n * @property { string } previousStepId\n * @property { Object } extraStepInfos\n *\n * @typedef { Object } HistoryMutationCharacterData\n * @property { \"characterData\" } type\n * @property { string } nodeId\n * @property { string } value\n * @property { string } oldValue\n *\n * @typedef { Object } HistoryMutationAttributes\n * @property { \"attributes\" } type\n * @property { string } nodeId\n * @property { string } attributeName\n * @property { string } value\n * @property { string } oldValue\n *\n * @typedef { Object } HistoryMutationClassList\n * @property { \"classList\" } type\n * @property { string } nodeId\n * @property { string } className\n * @property { boolean } value\n * @property { boolean } oldValue\n *\n * @typedef { Object } HistoryMutationAdd\n * @property { \"add\" } type\n * @property { string } nodeId\n * @property { string } parentNodeId\n * @property { SerializedNode } serializedNode\n * @property { string } nextNodeId\n * @property { string } previousNodeId\n *\n * @typedef { Object } HistoryMutationRemove\n * @property { \"remove\" } type\n * @property { string } nodeId\n * @property { string } parentNodeId\n * @property { SerializedNode } serializedNode\n * @property { string } nextNodeId\n * @property { string } previousNodeId\n *\n * @typedef { HistoryMutationCharacterData | HistoryMutationAttributes | HistoryMutationClassList | HistoryMutationAdd | HistoryMutationRemove } HistoryMutation\n *\n * @typedef {Object} MutationRecordClassList\n * @property { \"classList\" } type\n * @property { Node } target\n * @property { string } className\n * @property { boolean } oldValue\n * @property { boolean } value\n *\n * @typedef {Object} MutationRecordAttributes\n * @property { \"attributes\" } type\n * @property { Node } target\n * @property { string } attributeName\n * @property { string } oldValue\n * @property { string } value\n *\n * @typedef {Object} MutationRecordCharacterData\n * @property { \"characterData\" } type\n * @property { Node } target\n * @property { string } oldValue\n * @property { string } value\n *\n * @typedef {Object} Tree\n * @property {Node} node\n * @property {Tree[]} children\n *\n * @typedef {Object} MutationRecordChildList\n * @property { \"childList\" } type\n * @property { Node } target\n * @property { Node } previousSibling\n * @property { Node } nextSibling\n * @property { Tree[] } addedTrees\n * @property { Tree[] } removedTrees\n *\n * @typedef { MutationRecordClassList | MutationRecordAttributes | MutationRecordCharacterData | MutationRecordChildList } HistoryMutationRecord\n *\n * @typedef { Object } PreviewableOperation\n * @property { Function } commit\n * @property { Function } preview\n * @property { Function } revert\n */\n\n/**\n * @typedef { Object } HistoryShared\n * @property { HistoryPlugin['addCustomMutation'] } addCustomMutation\n * @property { HistoryPlugin['applyCustomMutation'] } applyCustomMutation\n * @property { HistoryPlugin['addExternalStep'] } addExternalStep\n * @property { HistoryPlugin['addStep'] } addStep\n * @property { HistoryPlugin['canRedo'] } canRedo\n * @property { HistoryPlugin['canUndo'] } canUndo\n * @property { HistoryPlugin['ignoreDOMMutations'] } ignoreDOMMutations\n * @property { HistoryPlugin['getHistorySteps'] } getHistorySteps\n * @property { HistoryPlugin['getNodeById'] } getNodeById\n * @property { HistoryPlugin['makePreviewableOperation'] } makePreviewableOperation\n * @property { HistoryPlugin['makePreviewableAsyncOperation'] } makePreviewableAsyncOperation\n * @property { HistoryPlugin['makeSavePoint'] } makeSavePoint\n * @property { HistoryPlugin['makeSnapshotStep'] } makeSnapshotStep\n * @property { HistoryPlugin['redo'] } redo\n * @property { HistoryPlugin['reset'] } reset\n * @property { HistoryPlugin['resetFromSteps'] } resetFromSteps\n * @property { HistoryPlugin['serializeSelection'] } serializeSelection\n * @property { HistoryPlugin['stageSelection'] } stageSelection\n * @property { HistoryPlugin['stageFocus'] } stageFocus\n * @property { HistoryPlugin['undo'] } undo\n * @property { HistoryPlugin['getIsPreviewing'] } getIsPreviewing\n * @property { HistoryPlugin['setStepExtra'] } setStepExtra\n * @property { HistoryPlugin['getIsCurrentStepModified'] } getIsCurrentStepModified\n */\n\n/**\n * @typedef {((record: HistoryMutationRecord) => void)[]} attribute_change_handlers\n * @typedef {(() => void)[]} before_add_step_handlers\n * @typedef {((records: HistoryMutationRecord[]) => void)[]} before_filter_mutation_record_handlers\n * @typedef {((root: HTMLElement) => void)[]} content_updated_handlers\n * @typedef {(() => void)[]} external_step_added_handlers\n * @typedef {((records: HistoryMutationRecord[], currentOperation: \"original\"|\"undo\"|\"redo\"|\"restore\") => void)[]} handleNewRecords\n * @typedef {(() => void)[]} history_cleaned_handlers\n * @typedef {(() => void)[]} history_reset_handlers\n * @typedef {(() => void)[]} history_reset_from_steps_handlers\n * @typedef {((revertedStep: HistoryStep) => void)[]} post_redo_handlers\n * @typedef {((revertedStep: HistoryStep) => void)[]} post_undo_handlers\n * @typedef {(() => void)[]} restore_savepoint_handlers\n * @typedef {((arg: { step: HistoryStep, stepCommonAncestor: HTMLElement, isPreviewing: boolean }) => void)[]} step_added_handlers\n *\n * @typedef {((record: HistoryMutationRecord) => boolean)[]} savable_mutation_record_predicates\n * @typedef {((step: HistoryStep) => boolean)[]} unreversible_step_predicates\n *\n * @typedef {((\n *    arg: {\n *      target: Node,\n *      attributeName: string,\n *      oldValue: string,\n *      value: string,\n *      reverse: boolean,\n *    },\n *    options: { forNewStep: boolean }\n *  ) => void)[]} attribute_change_processors\n * @typedef {((step: HistoryStep) => HistoryStep)[]} history_step_processors\n * @typedef {((node: Node, childTreesToSerialize: Tree[]) => Tree[])[]} serializable_descendants_processors\n * @typedef {((node: Node, attributeName: string, attributeValue: string) => boolean)[]} set_attribute_overrides\n */\n\nexport class HistoryPlugin extends Plugin {\n    static id = \"history\";\n    static dependencies = [\"selection\", \"sanitize\"];\n    static shared = [\n        \"addCustomMutation\",\n        \"applyCustomMutation\",\n        \"addExternalStep\",\n        \"addStep\",\n        \"canRedo\",\n        \"canUndo\",\n        \"ignoreDOMMutations\",\n        \"getHistorySteps\",\n        \"getNodeById\",\n        \"makePreviewableOperation\",\n        \"makePreviewableAsyncOperation\",\n        \"makeSavePoint\",\n        \"makeSnapshotStep\",\n        \"redo\",\n        \"reset\",\n        \"resetFromSteps\",\n        \"serializeSelection\",\n        \"stageSelection\",\n        \"stageFocus\",\n        \"undo\",\n        \"getIsPreviewing\",\n        \"setStepExtra\",\n        \"getIsCurrentStepModified\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"historyUndo\",\n                description: _t(\"Undo\"),\n                icon: \"fa-undo\",\n                run: this.undo.bind(this),\n            },\n            {\n                id: \"historyRedo\",\n                description: _t(\"Redo\"),\n                icon: \"fa-repeat\",\n                run: this.redo.bind(this),\n            },\n        ],\n        ...(hasTouch() && {\n            toolbar_groups: withSequence(5, { id: \"historyMobile\" }),\n            toolbar_items: [\n                {\n                    id: \"undo\",\n                    groupId: \"historyMobile\",\n                    commandId: \"historyUndo\",\n                    isDisabled: () => !this.canUndo(),\n                    namespaces: [\"compact\", \"expanded\"],\n                },\n                {\n                    id: \"redo\",\n                    groupId: \"historyMobile\",\n                    commandId: \"historyRedo\",\n                    isDisabled: () => !this.canRedo(),\n                    namespaces: [\"compact\", \"expanded\"],\n                },\n            ],\n        }),\n        shortcuts: [\n            { hotkey: \"control+z\", commandId: \"historyUndo\", global: true },\n            { hotkey: \"control+y\", commandId: \"historyRedo\", global: true },\n            { hotkey: \"control+shift+z\", commandId: \"historyRedo\", global: true },\n        ],\n        start_edition_handlers: () => {\n            this.enableObserver();\n            this.reset(this.config.content);\n        },\n        on_prepare_drag_handlers: this.disableIsCurrentStepModifiedWarning.bind(this),\n    };\n\n    setup() {\n        this.mutationFilteredClasses = new Set(this.getResource(\"system_classes\"));\n        this.mutationFilteredAttributes = new Set(this.getResource(\"system_attributes\"));\n        this._onKeyupResetContenteditableNodes = [];\n        this.addDomListener(this.document, \"beforeinput\", this._onDocumentBeforeInput.bind(this));\n        this.addDomListener(this.document, \"input\", this._onDocumentInput.bind(this));\n        this.addGlobalDomListener(\"pointerup\", (ev) => {\n            if (this.editable.contains(ev.target)) {\n                this.stageSelection();\n            }\n        });\n        this.observer = new MutationObserver((records) => this.handleNewRecords(records));\n        this.enableObserverCallbacks = new Set();\n        this._cleanups.push(() => this.observer.disconnect());\n        this.clean();\n    }\n\n    getIsPreviewing() {\n        return this.isPreviewing;\n    }\n\n    clean() {\n        this.handleObserverRecords();\n        /** @type { HistoryStep[] } */\n        this.steps = [];\n        /** @type { HistoryStep } */\n        this.currentStep = this.processHistoryStep({\n            selection: {},\n            mutations: [],\n            id: this.generateId(),\n            previousStepId: undefined,\n            extraStepInfos: {},\n        });\n        /** @type {Set<string>} Steps reverted by undo/redo operations */\n        this.revertedSteps = new Set();\n        /** @type {Set<string>} Steps reverted by restoring to a save point */\n        this.discardedSteps = new Set();\n        this.nodeMap = new NodeMap();\n        /** @type { WeakMap<Node, { attributes: Map<string, string>, classList: Map<string, boolean>, characterData: Map<string, string> }> } */\n        this.lastObservedState = new WeakMap();\n        this.setNodeId(this.editable);\n        this.dispatchTo(\"history_cleaned_handlers\");\n    }\n    /**\n     * @param {string} id\n     * @returns {Node}\n     */\n    getNodeById(id) {\n        return this.nodeMap.getNode(id);\n    }\n    /**\n     * Reset the history.\n     *\n     * @param { string } content\n     */\n    reset(content) {\n        this.clean();\n        this.stageSelection();\n        this.steps.push(this.makeSnapshotStep());\n        this.dispatchTo(\"history_reset_handlers\", content);\n    }\n    /**\n     * @param { HistoryStep[] } steps\n     */\n    resetFromSteps(steps) {\n        this.withObserverOff(() => {\n            this.editable.replaceChildren();\n            this.clean();\n            this.stageSelection();\n            for (const step of steps) {\n                this.applyMutations(step.mutations);\n            }\n            this.steps = steps;\n            // todo: to test\n            this.dispatchTo(\"history_reset_from_steps_handlers\");\n        });\n        this.dispatchTo(\"history_reset_from_steps_handlers\");\n    }\n    makeSnapshotStep() {\n        return {\n            selection: {\n                anchorNode: undefined,\n                anchorOffset: undefined,\n                focusNode: undefined,\n                focusOffset: undefined,\n            },\n            mutations: childNodes(this.editable)\n                .filter((node) => this.nodeMap.hasNode(node))\n                .map((node) => ({\n                    type: \"add\",\n                    parentNodeId: \"root\",\n                    nodeId: this.nodeMap.getId(node),\n                    serializedNode: this.serializeNode(node),\n                    nextNodeId: null,\n                })),\n            id: this.steps[this.steps.length - 1]?.id || this.generateId(),\n            previousStepId: undefined,\n        };\n    }\n\n    getHistorySteps() {\n        return this.steps;\n    }\n    /**\n     * @param { HistoryStep } step\n     */\n    processHistoryStep(step) {\n        for (const fn of this.getResource(\"history_step_processors\")) {\n            step = fn(step);\n        }\n        return step;\n    }\n\n    enableObserver() {\n        this.observer.observe(this.editable, {\n            childList: true,\n            subtree: true,\n            attributes: true,\n            attributeOldValue: true,\n            characterData: true,\n            characterDataOldValue: true,\n        });\n    }\n\n    /**\n     * Disable the mutation observer.\n     *\n     * /!\\ This method should be used with extreme caution. Not observing some\n     * mutations could lead to mutations that are impossible to undo/redo.\n     */\n    disableObserver() {\n        const enableObserver = () => {\n            this.enableObserverCallbacks.delete(enableObserver);\n            if (this.enableObserverCallbacks.size > 0) {\n                return;\n            }\n            this.handleObserverRecords();\n            this.isObserverDisabled = false;\n        };\n        this.enableObserverCallbacks.add(enableObserver);\n        this.handleObserverRecords();\n        this.isObserverDisabled = true;\n        return enableObserver;\n    }\n\n    /**\n     * Execute {@link callback} while the MutationObserver is disabled.\n     *\n     * /!\\ This method should be used with extreme caution. Not observing some\n     * mutations could lead to mutations that are impossible to undo/redo.\n     *\n     * /!\\ Do not re-introduce nodes that had been already added to the DOM in\n     * a history step. @see isObservedNode\n     *\n     * @param {Function} callback\n     */\n    ignoreDOMMutations(callback) {\n        const enableObserver = this.disableObserver();\n        try {\n            return callback();\n        } finally {\n            enableObserver();\n        }\n    }\n\n    /**\n     * This is not shared as it is only used internally by the history plugin.\n     * Other plugins should use {@link ignoreDOMMutations} instead.\n     */\n    withObserverOff(callback) {\n        this.handleObserverRecords();\n        this.observer.disconnect();\n        callback();\n        this.enableObserver();\n    }\n\n    handleObserverRecords(dispatch = true) {\n        this.handleNewRecords(this.observer.takeRecords(), dispatch);\n    }\n\n    /**\n     * @param { MutationRecord[] } mutationRecords\n     * @returns { HistoryMutationRecord[] }\n     */\n    processNewRecords(mutationRecords) {\n        if (this.observer.takeRecords().length) {\n            throw new Error(\"MutationObserver has pending records\");\n        }\n        mutationRecords = this.filterMutationRecords(mutationRecords);\n        /** @type {HistoryMutationRecord[]} */\n        let records = this.transformToHistoryMutationRecords(mutationRecords);\n        records = records.filter((record) => !this.isSystemMutationRecord(record));\n        records = this.filterAndAdjustHistoryMutationRecords(records);\n        this.stageRecords(records);\n        records\n            .filter(({ type }) => type === \"attributes\")\n            .forEach((record) => this.dispatchTo(\"attribute_change_handlers\", record));\n        return records;\n    }\n\n    /**\n     * @param {HistoryMutationRecord} record\n     */\n    isValidRecord(record) {\n        switch (record.type) {\n            case \"attributes\":\n            case \"classList\":\n            case \"characterData\":\n                // Filter out no-op\n                return record.value !== record.oldValue;\n            case \"childList\":\n                return (\n                    // Filter out no-op\n                    (record.addedTrees.length || record.removedTrees.length) &&\n                    // Filter out mutation without a valid position for node insertion\n                    (record.previousSibling !== undefined || record.nextSibling !== undefined)\n                );\n        }\n    }\n\n    dispatchContentUpdated() {\n        if (!this.currentStep?.mutations?.length) {\n            return;\n        }\n        // @todo @phoenix remove this?\n        // @todo @phoenix this includes previous mutations that were already\n        // stored in the current step. Ideally, it should only include the new ones.\n        const root = this.getMutationsRoot(this.currentStep.mutations);\n        if (!root) {\n            return;\n        }\n        this.dispatchTo(\"content_updated_handlers\", root);\n    }\n\n    /**\n     * @param { MutationRecord[] } records\n     * @param { boolean } [dispatch]\n     */\n    handleNewRecords(records, dispatch = true) {\n        const processedRecords = this.processNewRecords(records);\n        if (processedRecords.length) {\n            // TODO modify `handleMutations` of web_studio to handle\n            // `undoOperation`\n            if (dispatch) {\n                const stepType = this.currentStep.type;\n                this.dispatchTo(\"handleNewRecords\", processedRecords, stepType);\n            }\n            // Process potential new records adds by handleNewRecords.\n            this.processNewRecords(this.observer.takeRecords());\n            this.dispatchContentUpdated();\n        }\n    }\n\n    /**\n     * @param {HistoryMutationRecord} record\n     */\n    setIdOnAddedNodes(record) {\n        if (record.type !== \"childList\") {\n            return;\n        }\n        record.addedTrees\n            .flatMap(treeToNodes)\n            .filter((node) => !this.nodeMap.hasNode(node))\n            .forEach((node) => this.nodeMap.set(this.generateId(), node));\n    }\n\n    /**\n     * @param { MutationRecord[] } records\n     * @returns { MutationRecord[] }\n     */\n    filterMutationRecords(records) {\n        records = this.filterAttributeMutationRecords(records);\n        records = this.filterSameTextContentMutationRecords(records);\n        records = this.filterOutIntermediateStateMutationRecords(records);\n        return records;\n    }\n\n    /**\n     * @param { MutationRecord[] } records\n     */\n    filterAttributeMutationRecords(records) {\n        return records.filter((record) => {\n            if (record.type !== \"attributes\") {\n                return true;\n            }\n            // Skip the attributes change on the dom.\n            if (record.target === this.editable) {\n                return false;\n            }\n            if (record.attributeName === \"contenteditable\") {\n                return false;\n            }\n            return true;\n        });\n    }\n\n    /**\n     * @param { MutationRecord[] } records\n     * @returns { MutationRecord[] }\n     */\n    filterSameTextContentMutationRecords(records) {\n        const filteredRecords = [];\n        for (const record of records) {\n            if (record.type === \"childList\" && this.isSameTextContentMutation(record)) {\n                const { addedNodes, removedNodes } = record;\n                const oldId = this.nodeMap.getId(removedNodes[0]);\n                if (oldId) {\n                    this.nodeMap.set(oldId, addedNodes[0]);\n                    continue;\n                }\n            }\n            filteredRecords.push(record);\n        }\n        return filteredRecords;\n    }\n\n    /**\n     * Mutation records of type \"attribute\" and \"characterData\" provide the old\n     * value, but not the new value. When multiple mutations occur in the same\n     * batch for an element's attribute or characterData, we only know the final\n     * value of the accumulated changes, which is the DOM's current state.\n     *\n     *  The oldValue provided by mutations after the first one are intermediate\n     *  states that we do not care about. Discarding them allows us to store a\n     *  single record representing the accumulated changes, instead of\n     *  reconstructing the new value introduced by each mutation.\n     *\n     * @param { MutationRecord[] } records\n     */\n    filterOutIntermediateStateMutationRecords(records) {\n        // Keep track of visited attributes per each node\n        const isFirstAttributeOccurrence = trackOccurrencesPair();\n        // Keep track of visited nodes for characterData mutations\n        const isFirstCharDataOccurence = trackOccurrences();\n        const filteredRecords = [];\n        for (const record of records) {\n            if (record.type === \"attributes\") {\n                // Keep only the first mutation record for each (node, attribute) pair.\n                if (isFirstAttributeOccurrence(record.target, record.attributeName)) {\n                    filteredRecords.push(record);\n                }\n            } else if (record.type === \"characterData\") {\n                // Keep only the first charData mutation record for each node.\n                if (isFirstCharDataOccurence(record.target)) {\n                    filteredRecords.push(record);\n                }\n            } else {\n                filteredRecords.push(record);\n            }\n        }\n        return filteredRecords;\n    }\n\n    /**\n     * Transforms MutationRecords into HistoryMutationRecords.\n     *\n     * ChildList record have added/removed trees added to them.\n     * Class attribute records are expanded into multiple classList records.\n     * Attribute records have their oldValue normalized and new value added to it.\n     * CharacterData records have the new value added to it.\n     *\n     * @param {MutationRecord[]} records\n     * @returns {HistoryMutationRecord[]}\n     */\n    transformToHistoryMutationRecords(records) {\n        records = this.transformChildListRecords(records);\n        return records.flatMap((record) => {\n            if (record.type === \"attributes\") {\n                if (record.attributeName === \"class\") {\n                    return this.splitClassMutationRecord(record);\n                }\n                const oldValue = record.oldValue === undefined ? null : record.oldValue;\n                const value = record.target.getAttribute(record.attributeName);\n                return { ...pick(record, \"type\", \"target\", \"attributeName\"), oldValue, value };\n            }\n            if (record.type === \"characterData\") {\n                const value = record.target.textContent;\n                return { ...pick(record, \"type\", \"target\", \"oldValue\"), value };\n            }\n            return record;\n        });\n    }\n\n    /**\n     * ChildList mutation records do not contain information about the\n     * descendants of the added/removed nodes at the time of the mutation. This\n     * method transforms childList mutation records to include information about\n     * the added/removed trees.\n     *\n     * @param {MutationRecord[]} records\n     * @returns {(HistoryMutationRecord|MutationRecord)[]}\n     */\n    transformChildListRecords(records) {\n        /** @type {WeakMap<Node, Node[]>} */\n        const childListSnapshot = new WeakMap();\n        /** @type {(node: Node) => Node[]} */\n        const getChildListSnapshot = (node) => childListSnapshot.get(node) || childNodes(node);\n        /** @type {(node: Node) => Tree} */\n        const makeSnapshotTree = (node) => ({\n            node,\n            children: getChildListSnapshot(node).map(makeSnapshotTree),\n        });\n\n        // Reconstructs the child list before a mutation based on the state\n        // after it and the child list modifications\n        /** @type {(childListAfter: Node[], record: MutationRecord) => Node[]} */\n        const reconstructChildList = (childListAfter, record) => {\n            const { removedNodes, previousSibling, nextSibling } = record;\n            const previousSiblingNodes = previousSibling\n                ? childListAfter.slice(0, childListAfter.indexOf(previousSibling) + 1)\n                : [];\n            const nextSiblingNodes = nextSibling\n                ? childListAfter.slice(childListAfter.indexOf(nextSibling))\n                : [];\n            return [...previousSiblingNodes, ...removedNodes, ...nextSiblingNodes];\n        };\n\n        return records\n            .toReversed()\n            .map((/** @type {MutationRecord} */ record) => {\n                if (record.type !== \"childList\") {\n                    return record;\n                }\n                const transformedRecord = {\n                    ...pick(record, \"type\", \"previousSibling\", \"nextSibling\", \"target\"),\n                    addedTrees: [...record.addedNodes].map(makeSnapshotTree),\n                    removedTrees: [...record.removedNodes].map(makeSnapshotTree),\n                };\n                // Update snapshot for previous mutations\n                const childListAfterMutation = getChildListSnapshot(record.target);\n                const childListBefore = reconstructChildList(childListAfterMutation, record);\n                childListSnapshot.set(record.target, childListBefore);\n                return transformedRecord;\n            })\n            .toReversed();\n    }\n\n    /**\n     * Breaks down a class attribute mutation into individual class\n     * addition/removal records for more precise history tracking.\n     *\n     * @param { MutationRecord } record of type \"attributes\" with attributeName === \"class\"\n     * @returns { MutationRecordClassList[]}\n     */\n    splitClassMutationRecord(record) {\n        // oldValue can be nullish, or have extra spaces\n        const oldValue = record.oldValue?.split(\" \").filter(Boolean);\n        const classesBefore = new Set(oldValue);\n        const classesAfter = new Set(record.target.classList);\n        // @todo: use Set.prototype.difference when it becomes widely available\n        const setDifference = (setA, setB) => {\n            const diff = new Set(setA);\n            setB.forEach((item) => diff.delete(item));\n            return diff;\n        };\n        const addedClasses = setDifference(classesAfter, classesBefore);\n        const removedClasses = setDifference(classesBefore, classesAfter);\n\n        /** @type {(className: string, isAdded: boolean) => MutationRecordClassList } */\n        const createClassRecord = (className, isAdded) => ({\n            type: \"classList\",\n            target: record.target,\n            className,\n            value: isAdded,\n            oldValue: !isAdded,\n        });\n        // Generate records for each class change\n        return [\n            ...[...addedClasses].map((cls) => createClassRecord(cls, true)),\n            ...[...removedClasses].map((cls) => createClassRecord(cls, false)),\n        ];\n    }\n\n    /**\n     * @param { HistoryMutationRecord } record\n     */\n    isSystemMutationRecord(record) {\n        if (record.type === \"attributes\") {\n            return this.mutationFilteredAttributes.has(record.attributeName);\n        }\n        if (record.type === \"classList\") {\n            return this.mutationFilteredClasses.has(record.className);\n        }\n        return false;\n    }\n\n    /**\n     * If the observer is disabled, store the last observed state of the\n     * target's affected property (attribute/class/textContent) and drop the\n     * record.\n     *\n     * Otherwise (observer enabled), update the record as follows:\n     * - mutations targeting an unobserved node are dropped\n     * - mutations of type \"attributes\", \"classList\", and \"characterData\" have\n     * their `oldValue` adjusted to the last observed state of that target's\n     * property\n     * - mutations of type \"childList\" are updated to not include references to\n     * unobserved nodes.\n     *\n     * @param {HistoryMutationRecord[]} records\n     * @returns {HistoryMutationRecord[]}\n     */\n    filterAndAdjustHistoryMutationRecords(records) {\n        this.dispatchTo(\"before_filter_mutation_record_handlers\", records);\n        const savableRecordPredicates = this.getResource(\"savable_mutation_record_predicates\");\n        const isRecordSavable = (record) => savableRecordPredicates.every((p) => p(record));\n        const result = [];\n        for (const record of records) {\n            if (!this.isObservedNode(record.target)) {\n                continue;\n            }\n            if (this.isObserverDisabled || !isRecordSavable(record)) {\n                if (record.type !== \"childList\") {\n                    this.storeOldValue(record);\n                }\n                continue;\n            }\n            const updatedRecord =\n                record.type === \"childList\"\n                    ? this.updateChildListRecord(record)\n                    : this.updateOldValue(record);\n            if (this.isValidRecord(updatedRecord)) {\n                this.setIdOnAddedNodes(record);\n                result.push(updatedRecord);\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Any node that was added to the DOM without a mutation record in a history\n     * step (tipically due to {@link ignoreDOMMutations}) is considered an\n     * unobserved node.\n     *\n     * A known limitation to this approach is when a node that had been present\n     * in the editable before (and thus has an entry in the nodeMap) is re-added\n     * with {@link ignoreDOMMutations}. Such node will not be flagged as\n     * unobserved and history might become inconsistent.\n     *\n     * @param {Node} node\n     * @returns {boolean}\n     */\n    isObservedNode(node) {\n        return this.nodeMap.hasNode(node);\n    }\n\n    /**\n     * This function, alongside @see updateOldValue, ensures mutation records\n     * have the correct historical \"oldValue\" by checking against the last\n     * observed state.\n     *\n     * When the observer is disabled, we store the record's `oldValue` for a\n     * node's attribute/class/textContent as the last observed value.\n     *\n     * As multiple mutations to the same node-attribute/class/textContent can\n     * happen with the observer disabled, we store only the first value\n     * encountered for each node-attribute/class/text. This way, we capture the\n     * state as it was before any modifications in the disabled observer\n     * sequence began.\n     *\n     * @see updateOldValue\n     *\n     * @param {MutationRecordAttributes|MutationRecordClassList|MutationRecordCharacterData} record\n     */\n    storeOldValue(record) {\n        const { stateMap, key } = this.getObservedStateStorage(record);\n        // Only store it if not already stored.\n        if (!stateMap.has(key)) {\n            stateMap.set(key, record.oldValue);\n        }\n    }\n\n    /**\n     * This function, alongside @see storeOldValue, ensures mutation records\n     * have the correct historical \"oldValue\" by checking against the last\n     * observed state.\n     *\n     * When the observer is enabled, it updates a record's `oldValue` with the last\n     * observed state, and removes the entry to prevent reuse. Without removing\n     * the entry, the same historical value might be incorrectly applied to\n     * future mutation records targeting the same attribute/class of the same\n     * element, which would create incorrect history mutations.\n     *\n     * @param {MutationRecordAttributes|MutationRecordClassList|MutationRecordCharacterData} record\n     * @returns {MutationRecordAttributes|MutationRecordClassList|MutationRecordCharacterData}\n     */\n    updateOldValue(record) {\n        const { stateMap, key } = this.getObservedStateStorage(record);\n        if (!stateMap.has(key)) {\n            return record;\n        }\n        const lastObservedValue = stateMap.get(key);\n        // Remove entry, so it won't be used again.\n        stateMap.delete(key);\n        return { ...record, oldValue: lastObservedValue };\n    }\n\n    /**\n     * @param {HistoryMutationRecord} record\n     * @returns { { stateMap: Map, key: string } }\n     */\n    getObservedStateStorage(record) {\n        // Add entry for current target if not already present.\n        if (!this.lastObservedState.has(record.target)) {\n            this.lastObservedState.set(record.target, {\n                attributes: new Map(),\n                classList: new Map(),\n                characterData: new Map(),\n            });\n        }\n        const stateMap = this.lastObservedState.get(record.target)[record.type];\n        switch (record.type) {\n            case \"attributes\":\n                return { stateMap, key: record.attributeName };\n            case \"classList\":\n                return { stateMap, key: record.className };\n            case \"characterData\":\n                return { stateMap, key: \"textContent\" };\n            default:\n                throw new Error(`Unsupported mutation type: ${record.type}`);\n        }\n    }\n\n    /**\n     * @param {MutationRecordChildList} record\n     * @returns {MutationRecordChildList}\n     */\n    updateChildListRecord(record) {\n        // Invalidate sibling references to unobserved nodes\n        const isValidReference = (node) => node === null || this.isObservedNode(node);\n        const updateSibling = (sibling) => (isValidReference(sibling) ? sibling : undefined);\n        const previousSibling = updateSibling(record.previousSibling);\n        const nextSibling = updateSibling(record.nextSibling);\n\n        // Filter out unobserved nodes in removedTrees\n        const removeUnobservedNodes = (tree) => {\n            if (!this.isObservedNode(tree.node)) {\n                return null;\n            }\n            return {\n                node: tree.node,\n                children: tree.children.map(removeUnobservedNodes).filter(Boolean),\n            };\n        };\n        const removedTrees = record.removedTrees.map(removeUnobservedNodes).filter(Boolean);\n\n        return {\n            ...record,\n            previousSibling,\n            nextSibling,\n            removedTrees,\n        };\n    }\n\n    /**\n     * Check if a mutation consists of removing and adding a single text node\n     * with the same text content, which occurs in Firefox but is optimized\n     * away in Chrome.\n     *\n     * @param { MutationRecord } record\n     */\n    isSameTextContentMutation(record) {\n        const { addedNodes, removedNodes } = record;\n        return (\n            record.type === \"childList\" &&\n            addedNodes.length === 1 &&\n            removedNodes.length === 1 &&\n            addedNodes[0].nodeType === Node.TEXT_NODE &&\n            removedNodes[0].nodeType === Node.TEXT_NODE &&\n            addedNodes[0].textContent === removedNodes[0].textContent\n        );\n    }\n\n    /**\n     * Set the serialized selection of the currentStep.\n     *\n     * This method is used to save a serialized selection in the currentStep.\n     * It will be necessary if the step is reverted at some point because we need\n     * to set the selection to where it was before any mutation was made.\n     *\n     * It means that we should not call this method in the middle of mutations\n     * because if a selection is set onto a node that is edited/added/removed\n     * within the same step, it might become impossible to set the selection\n     * when reverting the step.\n     */\n    stageSelection() {\n        this.stageFocus();\n        const selection = this.dependencies.selection.getEditableSelection();\n        if (this.getIsCurrentStepModified()) {\n            console.warn(\n                `should not have any \"characterData\", \"remove\" or \"add\" mutations in current step when you update the selection`\n            );\n            return;\n        }\n        this.currentStep.selection = this.serializeSelection(selection);\n    }\n    /**\n     * Set the serialized focus of the currentStep.\n     */\n    stageFocus() {\n        let activeElement = this.document.activeElement;\n        if (activeElement.contains(this.editable)) {\n            activeElement = this.editable;\n        }\n        if (this.editable.contains(activeElement)) {\n            this.currentStep.activeElementId = this.setNodeId(activeElement);\n        }\n    }\n    /**\n     * @param { HistoryMutationRecord[] } records\n     */\n    stageRecords(records) {\n        for (const record of records) {\n            switch (record.type) {\n                case \"characterData\":\n                case \"classList\":\n                case \"attributes\": {\n                    const nodeId = this.nodeMap.getId(record.target);\n                    this.currentStep.mutations.push({ ...omit(record, \"target\"), nodeId });\n                    break;\n                }\n                case \"childList\": {\n                    this.currentStep.mutations.push(...this.splitChildListRecord(record));\n                    break;\n                }\n            }\n        }\n    }\n\n    /**\n     * @param {MutationRecordChildList} record\n     * @returns { (HistoryMutationRemove|HistoryMutationAdd)[] }\n     */\n    splitChildListRecord(record) {\n        const parentNodeId = this.nodeMap.getId(record.target);\n        if (!parentNodeId) {\n            throw new Error(\"Unknown parent node\");\n        }\n\n        const makeSingleNodeRecords = (trees, type) =>\n            trees.map((tree, index, treeList) => {\n                const node = tree.node;\n                const nodeList = treeList.map((t) => t.node);\n                const [previousSibling, nextSibling] =\n                    type === \"add\"\n                        ? [nodeList[index - 1] || record.previousSibling, record.nextSibling]\n                        : [record.previousSibling, nodeList[index + 1] || record.nextSibling];\n                const [nextNodeId, previousNodeId] = [nextSibling, previousSibling].map((sibling) =>\n                    // Preserve undefined and null values\n                    sibling ? this.nodeMap.getId(sibling) : sibling\n                );\n                const nodeId = this.nodeMap.getId(node);\n                const serializedNode = this.serializeTree(tree);\n                return { type, nodeId, parentNodeId, serializedNode, nextNodeId, previousNodeId };\n            });\n\n        return [\n            ...makeSingleNodeRecords(record.removedTrees, \"remove\"),\n            ...makeSingleNodeRecords(record.addedTrees, \"add\"),\n        ];\n    }\n\n    applyCustomMutation({ apply, revert }) {\n        apply();\n        this.addCustomMutation({ apply, revert });\n    }\n\n    addCustomMutation({ apply, revert }) {\n        const customMutation = {\n            type: \"custom\",\n            apply: () => {\n                apply();\n                this.addCustomMutation({ apply, revert });\n            },\n            revert: () => {\n                revert();\n                this.addCustomMutation({ apply: revert, revert: apply });\n            },\n        };\n        this.currentStep.mutations.push(customMutation);\n    }\n\n    /**\n     * @param { Node } node\n     */\n    setNodeId(node) {\n        let id = this.nodeMap.getId(node);\n        if (!id) {\n            id = node === this.editable ? \"root\" : this.generateId();\n            this.nodeMap.set(id, node);\n            node = node.firstChild;\n            while (node) {\n                this.setNodeId(node);\n                node = node.nextSibling;\n            }\n        }\n        return id;\n    }\n    generateId() {\n        // No need for secure random number.\n        return Math.floor(Math.random() * Math.pow(2, 52)).toString();\n    }\n\n    /**\n     * @param { Object } [params]\n     * @param { \"original\"|\"undo\"|\"redo\"|\"restore\" } [params.type]\n     * @param {Object} [params.extraStepInfos]\n     */\n    addStep({ type = \"original\", extraStepInfos } = {}) {\n        // @todo @phoenix should we allow to pause the making of a step?\n        // if (!this.stepsActive) {\n        //     return;\n        // }\n        // @todo @phoenix link zws plugin\n        // this._resetLinkZws();\n        // @todo @phoenix sanitize plugin\n        // this.sanitize();\n\n        // Set the state of the step here.\n        // That way, the state of undo and redo is truly accessible when\n        // executing the onChange callback.\n        // It is useful for external components if they execute shared.can[Undo|Redo]\n        const currentStep = this.currentStep;\n        currentStep.type = type;\n        this.handleObserverRecords();\n        const currentMutationsCount = currentStep.mutations.length;\n        if (currentMutationsCount === 0) {\n            return false;\n        }\n        const stepCommonAncestor = this.getMutationsRoot(currentStep.mutations) || this.editable;\n        this.dispatchTo(\"normalize_handlers\", stepCommonAncestor, type);\n        this.handleObserverRecords(false);\n        if (currentMutationsCount === currentStep.mutations.length) {\n            // If there was no registered mutation during the normalization step,\n            // force the dispatch of a content_updated to allow i.e. the hint\n            // plugin to react to non-observed changes (i.e. a div becoming\n            // a baseContainer).\n            this.dispatchContentUpdated();\n        }\n\n        currentStep.previousStepId = this.steps.at(-1)?.id;\n\n        currentStep.selectionAfter = this.serializeSelection(\n            this.dependencies.selection.getEditableSelection()\n        );\n        this.steps.push(currentStep);\n        // @todo @phoenix add this in the linkzws plugin.\n        // this._setLinkZws();\n        this.dispatchTo(\"before_add_step_handlers\");\n        if (extraStepInfos) {\n            currentStep.extraStepInfos = extraStepInfos;\n        }\n        this.currentStep = this.processHistoryStep({\n            id: this.generateId(),\n            type: undefined,\n            selection: {},\n            mutations: [],\n            previousStepId: undefined,\n            extraStepInfos: {},\n        });\n        this.stageSelection();\n        this.dispatchTo(\"step_added_handlers\", {\n            step: currentStep,\n            stepCommonAncestor,\n            isPreviewing: this.isPreviewing,\n        });\n        this.config.onChange?.({ isPreviewing: this.isPreviewing });\n        return currentStep;\n    }\n    canUndo() {\n        return this.getNextUndoIndex() > 0;\n    }\n    canRedo() {\n        return this.getNextRedoIndex() > 0;\n    }\n    undo() {\n        if (this.steps.length === 1) {\n            return;\n        }\n        this.handleObserverRecords();\n        // The last step is considered an uncommited draft so always revert it.\n        const lastStep = this.currentStep;\n        this.revertMutations(lastStep.mutations);\n        // Discard mutations generated by the revert.\n        this.observer.takeRecords();\n        // Clean the last step otherwise if no other step is created after, the\n        // mutations of the revert itself will be added to the same step and\n        // grow exponentially at each undo.\n        lastStep.mutations = [];\n\n        const pos = this.getNextUndoIndex();\n        let revertedStep;\n        if (pos > 0) {\n            revertedStep = this.steps[pos];\n            this.revertedSteps.add(revertedStep.id);\n            this.revertMutations(revertedStep.mutations, { forNewStep: true });\n            this.setSerializedFocus(revertedStep.activeElementId);\n            this.stageFocus();\n            this.setSerializedSelection(revertedStep.selection);\n            this.currentStep.selection = revertedStep.selectionAfter;\n            this.addStep({ type: \"undo\", extraStepInfos: revertedStep.extraStepInfos });\n            // Consider the last position of the history as an undo.\n        }\n        this.dispatchTo(\"post_undo_handlers\", revertedStep);\n    }\n    redo() {\n        this.handleObserverRecords();\n        // Current step is considered an uncommitted draft, so revert it,\n        // otherwise a redo would not be possible.\n        this.revertMutations(this.currentStep.mutations);\n        // Discard mutations generated by the revert.\n        this.observer.takeRecords();\n        // At this point, _currentStep.mutations contains the current step's\n        // mutations plus the ones that revert it, with net effect zero.\n        this.currentStep.mutations = [];\n\n        const pos = this.getNextRedoIndex();\n        let revertedStep;\n        if (pos > 0) {\n            revertedStep = this.steps[pos];\n            this.revertedSteps.add(revertedStep.id);\n            this.revertMutations(revertedStep.mutations, { forNewStep: true });\n            this.setSerializedFocus(revertedStep.activeElementId);\n            this.stageFocus();\n            this.setSerializedSelection(revertedStep.selection);\n            this.currentStep.selection = revertedStep.selectionAfter;\n            this.addStep({ type: \"redo\", extraStepInfos: revertedStep.extraStepInfos });\n        }\n        this.dispatchTo(\"post_redo_handlers\", revertedStep);\n    }\n    /**\n     * @param { SerializedSelection } selection\n     */\n    setSerializedSelection(selection) {\n        if (!selection.anchorNodeId) {\n            return;\n        }\n        const anchorNode = this.nodeMap.getNode(selection.anchorNodeId);\n        if (!anchorNode) {\n            return;\n        }\n        const newSelection = {\n            anchorNode,\n            anchorOffset: selection.anchorOffset,\n        };\n        const focusNode = this.nodeMap.getNode(selection.focusNodeId);\n        if (focusNode) {\n            newSelection.focusNode = focusNode;\n            newSelection.focusOffset = selection.focusOffset;\n        }\n        this.dependencies.selection.setSelection(newSelection, { normalize: false });\n        // @todo @phoenix add this in the selection or table plugin.\n        // // If a table must be selected, ensure it's in the same tick.\n        // this._handleSelectionInTable();\n    }\n    /**\n     * @param { string } activeElementId\n     */\n    setSerializedFocus(activeElementId) {\n        const elementToFocus =\n            activeElementId === \"root\"\n                ? this.editable\n                : activeElementId && this.nodeMap.getNode(activeElementId);\n        if (elementToFocus?.isConnected && elementToFocus !== this.document.activeElement) {\n            elementToFocus.focus();\n        }\n    }\n    /**\n     * Get the step index in the history to undo.\n     * Return -1 if no undo index can be found.\n     */\n    getNextUndoIndex() {\n        // Go back to first step that can be undone (\"original\" or \"redo\").\n        for (let index = this.steps.length - 1; index >= 0; index--) {\n            const step = this.steps[index];\n            if (!this.isReversibleStep(index) || this.discardedSteps.has(step.id)) {\n                continue;\n            }\n            if ([\"original\", \"redo\"].includes(step.type) && !this.revertedSteps.has(step.id)) {\n                return index;\n            }\n        }\n        // There is no steps left to be undone, return an index that does not\n        // point to any step\n        return -1;\n    }\n    /**\n     * Meant to be overriden.\n     *\n     * @param { number } index\n     */\n    isReversibleStep(index) {\n        const step = this.steps[index];\n        if (!step) {\n            return false;\n        }\n        return !this.getResource(\"unreversible_step_predicates\").some((predicate) =>\n            predicate(step)\n        );\n    }\n    /**\n     * Get the step index in the history to redo.\n     * Return -1 if no redo index can be found.\n     */\n    getNextRedoIndex() {\n        // Look for an \"undo\" step that has not yet been redone. Stop search if\n        // a \"original\" step is found.\n        for (let index = this.steps.length - 1; index >= 0; index--) {\n            const step = this.steps[index];\n            if (!this.isReversibleStep(index) || this.discardedSteps.has(step.id)) {\n                continue;\n            }\n            if (step.type === \"original\") {\n                return -1;\n            }\n            if (step.type === \"undo\" && !this.revertedSteps.has(step.id)) {\n                return index;\n            }\n        }\n        return -1;\n    }\n    /**\n     * Insert a step in the history.\n     *\n     * @param { HistoryStep } newStep\n     * @param { number } index\n     */\n    addExternalStep(newStep, index) {\n        this.withObserverOff(() => {\n            // The last step is an uncommited draft, revert it first\n            this.revertMutations(this.currentStep.mutations);\n\n            const stepsAfterNewStep = this.steps.slice(index);\n\n            for (const stepToRevert of stepsAfterNewStep.slice().reverse()) {\n                this.revertMutations(stepToRevert.mutations);\n            }\n            this.applyMutations(newStep.mutations);\n            this.dispatchTo(\n                \"normalize_handlers\",\n                this.getMutationsRoot(newStep.mutations) || this.editable\n            );\n            this.steps.splice(index, 0, newStep);\n            for (const stepToApply of stepsAfterNewStep) {\n                this.applyMutations(stepToApply.mutations);\n            }\n            // Reapply the uncommited draft, since this is not an operation which should cancel it\n            this.applyMutations(this.currentStep.mutations);\n            this.dispatchTo(\"external_step_added_handlers\");\n        });\n    }\n    /**\n     * @param { HistoryMutation[] } mutations\n     * @param { Object } options\n     * @param { boolean } options.forNewStep whether the mutations will be used\n     *        to create a new step\n     * @param { boolean } options.reverse whether the mutations are the reverse\n     *        of other mutations\n     */\n    applyMutations(mutations, { forNewStep = false, reverse } = {}) {\n        if (forNewStep) {\n            this.fixClassListMutationsForNewStep(mutations);\n        }\n        for (const mutation of mutations) {\n            switch (mutation.type) {\n                case \"custom\": {\n                    mutation.apply();\n                    break;\n                }\n                case \"characterData\": {\n                    const node = this.nodeMap.getNode(mutation.nodeId);\n                    if (node) {\n                        node.textContent = mutation.value;\n                    }\n                    break;\n                }\n                case \"classList\": {\n                    const node = this.nodeMap.getNode(mutation.nodeId);\n                    if (node) {\n                        toggleClass(node, mutation.className, mutation.value);\n                    }\n                    break;\n                }\n                case \"attributes\": {\n                    const node = this.nodeMap.getNode(mutation.nodeId);\n                    if (node) {\n                        let value = mutation.value;\n                        for (const cb of this.getResource(\"attribute_change_processors\")) {\n                            value = cb(\n                                {\n                                    target: node,\n                                    attributeName: mutation.attributeName,\n                                    oldValue: mutation.oldValue,\n                                    value,\n                                    reverse,\n                                },\n                                { forNewStep }\n                            );\n                        }\n                        this.setAttribute(node, mutation.attributeName, value);\n                    }\n                    break;\n                }\n                case \"remove\": {\n                    this.applyRemoveMutation(mutation);\n                    break;\n                }\n                case \"add\": {\n                    this.applyAddMutation(mutation);\n                    break;\n                }\n            }\n        }\n    }\n\n    /**\n     * When applying mutations for a new step, we expect them to produce\n     * observable mutations, which will then be stored in a new step. However,\n     * there are situations where applying a classList mutation would not\n     * produce an observable mutation:\n     * - adding a class that is already present\n     * - removing a class that is already absent\n     * These scenarios might happen due to the class having been already added\n     * or removed by a previous unobserved mutation. We want, nevertheless to\n     * produce the observable mutation of adding/removing this class, as this\n     * does correspond to a state change in observable history and should be\n     * included in the new step. In order to produce such observable mutations,\n     * we set the dom state to the one that would produce the desired result.\n     * This is equivalent to restoring the dom to the observed state in recorded\n     * history before applying a mutation, that is, oldValue (as oldValue is\n     * always !value for staged classList records).\n     *\n     * @param { HistoryMutation[] } mutations\n     */\n    fixClassListMutationsForNewStep(mutations) {\n        const isFirstOcurrence = trackOccurrencesPair();\n        // Mutations that when applied would not produce observable classList mutations\n        const nonObservableClassMutations = mutations\n            .filter((mutation) => mutation.type === \"classList\")\n            .filter(({ nodeId, className }) => isFirstOcurrence(nodeId, className))\n            .map((mutation) => ({ ...mutation, node: this.nodeMap.getNode(mutation.nodeId) }))\n            .filter(({ node, className, value }) => value === node?.classList.contains(className));\n        if (nonObservableClassMutations.length) {\n            const setToOldValue = ({ node, className, oldValue }) =>\n                toggleClass(node, className, oldValue);\n            this.withObserverOff(() => nonObservableClassMutations.forEach(setToOldValue));\n        }\n    }\n\n    /**\n     * @param {HistoryMutationRemove} mutation\n     */\n    applyRemoveMutation(mutation) {\n        const parent = this.nodeMap.getNode(mutation.parentNodeId);\n        const toRemove = this.nodeMap.getNode(mutation.nodeId);\n        if (!toRemove) {\n            console.warn(\"Mutation could not be applied, node to remove is unknown.\", mutation);\n            return;\n        }\n        if (toRemove.parentElement !== parent) {\n            console.warn(\"Mutation could not be applied, parent node does not match.\", mutation);\n            return;\n        }\n        toRemove.remove();\n    }\n\n    /**\n     * @param {HistoryMutationAdd} mutation\n     */\n    applyAddMutation(mutation) {\n        const { nodeId, serializedNode, parentNodeId, nextNodeId, previousNodeId } = mutation;\n\n        const toAdd = this.nodeMap.getNode(nodeId) || this.unserializeNode(serializedNode);\n        if (!toAdd) {\n            return;\n        }\n\n        const parent = this.nodeMap.getNode(parentNodeId);\n        if (!parent) {\n            console.warn(\"Mutation could not be applied, parent node is missing.\", mutation);\n            return;\n        }\n        if (previousNodeId === null) {\n            parent.prepend(toAdd);\n            return;\n        }\n        if (nextNodeId === null) {\n            parent.append(toAdd);\n            return;\n        }\n        const isValid = (node) => node?.parentNode === parent;\n        const previousNode = this.nodeMap.getNode(previousNodeId);\n        if (isValid(previousNode)) {\n            previousNode.after(toAdd);\n            return;\n        }\n        const nextNode = this.nodeMap.getNode(nextNodeId);\n        if (isValid(nextNode)) {\n            nextNode.before(toAdd);\n            return;\n        }\n        console.warn(\"Mutation could not be applied, reference nodes are invalid.\", mutation);\n    }\n\n    revertMutations(mutations, { forNewStep = false } = {}) {\n        const revertedMutations = mutations.map((mutation) => {\n            switch (mutation.type) {\n                case \"characterData\":\n                case \"classList\":\n                case \"attributes\":\n                    return { ...mutation, value: mutation.oldValue, oldValue: mutation.value };\n                case \"remove\":\n                    return { ...mutation, type: \"add\" };\n                case \"add\":\n                    return { ...mutation, type: \"remove\" };\n                case \"custom\":\n                    return { ...mutation, apply: mutation.revert, revert: mutation.apply };\n                default:\n                    throw new Error(`Unknown mutation type: ${mutation.type}`);\n            }\n        });\n        this.applyMutations(revertedMutations.toReversed(), { forNewStep, reverse: true });\n    }\n\n    /**\n     * Serialize an editor selection.\n     * @param { EditorSelection } selection\n     * @returns { SerializedSelection }\n     */\n    serializeSelection(selection) {\n        return {\n            anchorNodeId: this.nodeMap.getId(selection.anchorNode),\n            anchorOffset: selection.anchorOffset,\n            focusNodeId: this.nodeMap.getId(selection.focusNode),\n            focusOffset: selection.focusOffset,\n        };\n    }\n    /**\n     * Returns the deepest common ancestor element of the given mutations.\n     * @param {HistoryMutation[]} mutations - The array of mutations.\n     * @returns {HTMLElement|null} - The common ancestor element.\n     */\n    getMutationsRoot(mutations) {\n        const nodes = mutations\n            .map((m) => this.nodeMap.getNode(m.parentNodeId || m.nodeId))\n            .filter((node) => this.editable.contains(node));\n        let commonAncestor = getCommonAncestor(nodes, this.editable);\n        if (commonAncestor?.nodeType === Node.TEXT_NODE) {\n            commonAncestor = commonAncestor.parentElement;\n        }\n        return commonAncestor;\n    }\n    /**\n     * Returns a function that can be later called to revert history to the\n     * current state.\n     * @returns {Function}\n     */\n    makeSavePoint() {\n        this.handleObserverRecords();\n        const draftMutations = this.currentStep.mutations.slice();\n        const step = this.steps.at(-1);\n        let applied = false;\n        // TODO ABD TODO @phoenix: selection may become obsolete, it should evolve with mutations.\n        const selectionToRestore = this.dependencies.selection.preserveSelection();\n        const extraToRestore = { ...this.currentStep.extraStepInfos };\n        return () => {\n            if (applied) {\n                return;\n            }\n            applied = true;\n            const stepIndex = this.steps.findLastIndex((item) => item === step);\n            this.restoreToStep(stepIndex);\n            // Apply draft mutations to recover the same currentStep state\n            // as before.\n            this.applyMutations(draftMutations, { forNewStep: true });\n            this.handleObserverRecords();\n            // TODO ABD TODO @phoenix: evaluate if the selection is not restorable at the desired position\n            selectionToRestore.restore();\n            this.currentStep.extraStepInfos = extraToRestore;\n            this.dispatchTo(\"restore_savepoint_handlers\");\n        };\n    }\n    /**\n     * Creates a set of functions to preview, apply, and revert an operation.\n     * @param {Function} operation\n     * @returns {PreviewableOperation}\n     */\n    makePreviewableOperation(operation) {\n        let revertOperation = () => {};\n\n        return {\n            preview: (...args) => {\n                revertOperation();\n                revertOperation = this.makeSavePoint();\n                this.isPreviewing = true;\n                this.stageSelection();\n                operation(...args);\n                // todo: We should not add a step on preview as it would send\n                // unnecessary steps in collaboration and let the other peer see\n                // what we preview.\n                //\n                // The operation should be similar than in the 'commit'\n                // (normalize etc...) hence the 'addStep' (but we need to remove\n                // it for the collaboration).\n                this.addStep();\n            },\n            commit: (...args) => {\n                revertOperation();\n                this.isPreviewing = false;\n                operation(...args);\n                this.addStep();\n            },\n            revert: () => {\n                revertOperation();\n                revertOperation = () => {};\n                this.isPreviewing = false;\n            },\n        };\n    }\n\n    /**\n     * Creates a set of functions to preview, apply, and revert an async operation.\n     * @param {Function} operation\n     * @returns {PreviewableOperation}\n     */\n    makePreviewableAsyncOperation(operation) {\n        let revertOperation = () => {};\n\n        return {\n            preview: async (...args) => {\n                await revertOperation();\n                const def = new Deferred();\n                const revertSavePoint = this.makeSavePoint();\n                revertOperation = async () => {\n                    await def;\n                    revertSavePoint();\n                };\n                this.isPreviewing = true;\n                try {\n                    await operation(...args);\n                } catch (error) {\n                    revertSavePoint();\n                    throw error;\n                } finally {\n                    def.resolve();\n                }\n                if (this.isDestroyed) {\n                    return;\n                }\n                // todo: We should not add a step on preview as it would send\n                // unnecessary steps in collaboration and let the other peer see\n                // what we preview.\n                //\n                // The operation should be similar than in the 'commit'\n                // (normalize etc...) hence the 'addStep' (but we need to remove\n                // it for the collaboration).\n                this.addStep();\n            },\n            commit: async (...args) => {\n                await revertOperation();\n                this.isPreviewing = false;\n                const revertSavePoint = this.makeSavePoint();\n                try {\n                    await operation(...args);\n                } catch (error) {\n                    revertSavePoint();\n                    throw error;\n                }\n                if (this.isDestroyed) {\n                    return;\n                }\n                this.addStep();\n            },\n            revert: async () => {\n                await revertOperation();\n                revertOperation = () => {};\n                this.isPreviewing = false;\n            },\n        };\n    }\n\n    /**\n     * Restores the editable to the state of a previous step.\n     * It does so by discarding the current draft and reverting reversible steps\n     * until the specified step index, while ensuring that irreversible steps\n     * are maintained. This will add a new \"restore\" step and set the reverted\n     * steps's state to \"discarded\".\n     *\n     * @param {Number} stepIndex\n     */\n    restoreToStep(stepIndex) {\n        // Discard current draft.\n        this.handleObserverRecords();\n        this.revertMutations(this.currentStep.mutations);\n        this.observer.takeRecords();\n        this.currentStep.mutations = [];\n        let lastRevertedStep = this.currentStep;\n\n        if (stepIndex === this.steps.length - 1) {\n            return;\n        }\n        // Revert all mutations until stepIndex, and mark all reversible\n        // steps as \"discarded\" in the process (typically current peer steps).\n        for (let i = this.steps.length - 1; i > stepIndex; i--) {\n            const currentStep = this.steps[i];\n            this.revertMutations(currentStep.mutations, { forNewStep: true });\n            // Process (filter, handle and stage) mutations so that the\n            // attribute comparison for the state change is done with the\n            // intermediate attribute value and not with the final value in the\n            // DOM after all steps were reverted then applied again.\n            this.processNewRecords(this.observer.takeRecords());\n            if (this.isReversibleStep(i)) {\n                this.discardedSteps.add(currentStep.id);\n                lastRevertedStep = currentStep;\n            }\n        }\n        // Re-apply every non reversible steps (typically collaborators steps).\n        for (let i = stepIndex + 1; i < this.steps.length; i++) {\n            const currentStep = this.steps[i];\n            if (!this.isReversibleStep(i)) {\n                this.applyMutations(currentStep.mutations, { forNewStep: true });\n                this.processNewRecords(this.observer.takeRecords());\n            }\n        }\n        // TODO ABD TODO @phoenix: review selections, this selection could be obsolete\n        // depending on the non-reversible steps that were applied.\n        this.setSerializedSelection(lastRevertedStep.selection);\n        // Register resulting mutations as a new \"restore\" step (prevent undo).\n        this.dispatchContentUpdated();\n        this.addStep({ type: \"restore\" });\n    }\n\n    setStepExtra(key, value) {\n        this.currentStep.extraStepInfos[key] = value;\n    }\n\n    disableIsCurrentStepModifiedWarning() {\n        this.ignoreIsCurrentStepModified = true;\n        return () => {\n            this.ignoreIsCurrentStepModified = false;\n        };\n    }\n\n    getIsCurrentStepModified() {\n        if (this.ignoreIsCurrentStepModified) {\n            return false;\n        }\n        return this.currentStep.mutations.find((m) =>\n            [\"characterData\", \"remove\", \"add\"].includes(m.type)\n        );\n    }\n\n    /**\n     * @param { Node } node\n     * @param { string } attributeName\n     * @param { string } attributeValue\n     */\n    setAttribute(node, attributeName, attributeValue) {\n        if (this.delegateTo(\"set_attribute_overrides\", node, attributeName, attributeValue)) {\n            return;\n        }\n\n        // if attributeValue is falsy but not null, we still need to apply it\n        if (attributeValue !== null) {\n            node.setAttribute(attributeName, attributeValue);\n        } else {\n            node.removeAttribute(attributeName);\n        }\n    }\n    /**\n     * Serialize a node and its children.\n     * @param { Node } node\n     */\n    serializeNode(node) {\n        return this.serializeTree(nodeToTree(node));\n    }\n    /**\n     * Unserialize a node and its children.\n     *\n     * @param { SerializedNode } node\n     * @returns { Node }\n     */\n    unserializeNode(node) {\n        let [unserializedNode, newNodesMap] = this._unserializeNode(node, this.nodeMap);\n        if (!unserializedNode) {\n            return null;\n        }\n        const fakeNode = this.document.createElement(\"fake-el\");\n        fakeNode.appendChild(unserializedNode);\n        this.dependencies.sanitize.sanitize(fakeNode, { IN_PLACE: true });\n        unserializedNode = fakeNode.firstChild;\n        if (!unserializedNode) {\n            return null;\n        }\n        // Only assing id to the remaining nodes, otherwise the removed nodes\n        // will still be accessible through the nodeMap and could lead to\n        // security issues.\n        for (const node of [unserializedNode, ...descendants(unserializedNode)]) {\n            if (this.nodeMap.hasNode(node)) {\n                continue;\n            }\n            const id = newNodesMap.get(node);\n            if (id) {\n                this.nodeMap.set(id, node);\n            }\n        }\n        return unserializedNode;\n    }\n\n    /**\n     * @param {Tree} tree\n     * @returns {SerializedNode|null}\n     */\n    serializeTree(tree) {\n        const node = tree.node;\n        const nodeId = this.nodeMap.getId(node);\n        if (!nodeId) {\n            return null;\n        }\n        const result = {\n            nodeType: node.nodeType,\n            nodeId: nodeId,\n        };\n        if (node.nodeType === Node.TEXT_NODE) {\n            result.textValue = node.nodeValue;\n        } else if (node.nodeType === Node.ELEMENT_NODE) {\n            let childTreesToSerialize = tree.children;\n            for (const cb of this.getResource(\"serializable_descendants_processors\")) {\n                childTreesToSerialize = cb(node, childTreesToSerialize);\n            }\n            result.tagName = node.tagName;\n            result.attributes = Object.fromEntries(\n                [...node.attributes].map((attr) => [attr.name, attr.value])\n            );\n            result.children = childTreesToSerialize\n                .map((tree) => this.serializeTree(tree))\n                .filter(Boolean);\n        }\n        return result;\n    }\n    /**\n     * Unserialize a node and its children.\n     * @param { SerializedNode } serializedNode\n     * @param { NodeMap} nodeMap\n     * @param { Map<Node, string> } _map\n     * @returns { [Node, Map<Node, string>] }\n     */\n    _unserializeNode(serializedNode, nodeMap = new NodeMap(), _map = new Map()) {\n        let node = nodeMap.getNode(serializedNode.nodeId);\n        if (node) {\n            return [node, _map];\n        }\n        if (serializedNode.nodeType === Node.TEXT_NODE) {\n            node = this.document.createTextNode(serializedNode.textValue);\n        } else if (serializedNode.nodeType === Node.ELEMENT_NODE) {\n            node = this.document.createElement(serializedNode.tagName);\n            for (const key in serializedNode.attributes) {\n                node.setAttribute(key, serializedNode.attributes[key]);\n            }\n            node.append(\n                ...serializedNode.children\n                    .map((child) => this._unserializeNode(child, nodeMap, _map)[0])\n                    .filter(Boolean)\n            );\n        } else {\n            console.warn(\"unknown node type\");\n            return [null, _map];\n        }\n        _map.set(node, serializedNode.nodeId);\n        return [node, _map];\n    }\n\n    _onDocumentBeforeInput(ev) {\n        if (this.editable.contains(ev.target)) {\n            return;\n        }\n        if ([\"historyUndo\", \"historyRedo\"].includes(ev.inputType)) {\n            this._onKeyupResetContenteditableNodes.push(\n                ...this.editable.querySelectorAll(\"[contenteditable=true]\")\n            );\n            if (this.editable.getAttribute(\"contenteditable\") === \"true\") {\n                this._onKeyupResetContenteditableNodes.push(this.editable);\n            }\n\n            for (const node of this._onKeyupResetContenteditableNodes) {\n                node.setAttribute(\"contenteditable\", false);\n            }\n        }\n    }\n\n    _onDocumentInput(ev) {\n        if (\n            [\"historyUndo\", \"historyRedo\"].includes(ev.inputType) &&\n            this._onKeyupResetContenteditableNodes.length\n        ) {\n            for (const node of this._onKeyupResetContenteditableNodes) {\n                node.setAttribute(\"contenteditable\", true);\n            }\n            this._onKeyupResetContenteditableNodes = [];\n        }\n    }\n}\n\n/**\n * @param {Node} node\n * @returns {Tree}\n */\nexport function nodeToTree(node) {\n    return {\n        node,\n        children: childNodes(node).map(nodeToTree),\n    };\n}\n\n/**\n * @param {Tree} tree\n * @returns {Node[]}\n */\nfunction treeToNodes(tree) {\n    return [tree.node, ...tree.children.flatMap(treeToNodes)];\n}\n\n/**\n * Bidirectional map between IDs (string) and Node objects.\n */\nclass NodeMap {\n    constructor() {\n        // Private properties enclosed in the constructor\n        /** @type {Map<string, Node>} */\n        const idToNodeMap = new Map();\n        /** @type {Map<Node, string>} */\n        const nodeToIdMap = new Map();\n\n        // Public methods\n        /** @type {(id: string, node: Node) => void} */\n        this.set = (id, node) => {\n            if (!id || !node) {\n                throw new Error(\"Id and Node cannot be nullish\");\n            }\n            // Remove old mappings\n            const oldNode = idToNodeMap.get(id);\n            nodeToIdMap.delete(oldNode);\n            const oldId = nodeToIdMap.get(node);\n            idToNodeMap.delete(oldId);\n            // Set new mappings\n            idToNodeMap.set(id, node);\n            nodeToIdMap.set(node, id);\n        };\n\n        /** @type {(id: string) => Node | undefined} */\n        this.getNode = (id) => idToNodeMap.get(id);\n\n        /** @type {(node: Node) => string | undefined} */\n        this.getId = (node) => nodeToIdMap.get(node);\n\n        /** @type {(node: Node) => boolean} */\n        this.hasNode = (node) => nodeToIdMap.has(node);\n    }\n}\n", "import { Plugin } from \"../plugin\";\n\n/**\n * @typedef {((ev: InputEvent) => void)[]} beforeinput_handlers\n * @typedef {((ev: InputEvent) => void)[]} input_handlers\n */\n\nexport class InputPlugin extends Plugin {\n    static id = \"input\";\n    static dependencies = [\"history\"];\n    setup() {\n        this.addDomListener(this.editable, \"beforeinput\", this.onBeforeInput);\n        this.addDomListener(this.editable, \"input\", this.onInput);\n    }\n\n    onBeforeInput(ev) {\n        this.dependencies.history.stageSelection();\n        this.dispatchTo(\"beforeinput_handlers\", ev);\n    }\n\n    onInput(ev) {\n        this.dependencies.history.addStep();\n        this.dispatchTo(\"input_handlers\", ev);\n    }\n}\n", "import { splitTextNode } from \"@html_editor/utils/dom\";\nimport { Plugin } from \"../plugin\";\nimport { CTGROUPS, CTYPES } from \"../utils/content_types\";\nimport { getState, isFakeLineBreak, prepareUpdate } from \"../utils/dom_state\";\nimport { DIRECTIONS, leftPos, rightPos } from \"../utils/position\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { closestBlock, isBlock } from \"../utils/blocks\";\nimport { nextLeaf } from \"../utils/dom_info\";\n\n/**\n * @typedef { Object } LineBreakShared\n * @property { LineBreakPlugin['insertLineBreak'] } insertLineBreak\n * @property { LineBreakPlugin['insertLineBreakElement'] } insertLineBreakElement\n * @property { LineBreakPlugin['insertLineBreakNode'] } insertLineBreakNode\n */\n\n/**\n * @typedef {(() => void)[]} before_line_break_handlers\n * @typedef {((params: { targetNode: Element, targetOffset: number }) => void | true)[]} insert_line_break_element_overrides\n */\n\nexport class LineBreakPlugin extends Plugin {\n    static dependencies = [\"selection\", \"history\", \"input\", \"delete\"];\n    static id = \"lineBreak\";\n    static shared = [\"insertLineBreak\", \"insertLineBreakNode\", \"insertLineBreakElement\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        beforeinput_handlers: this.onBeforeInput.bind(this),\n        legit_feff_predicates: [\n            (node) =>\n                !node.nextSibling &&\n                !isBlock(closestElement(node)) &&\n                nextLeaf(node, closestBlock(node)),\n        ],\n    };\n\n    insertLineBreak() {\n        this.dispatchTo(\"before_line_break_handlers\");\n        let selection = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        if (!selection.isCollapsed) {\n            // @todo @phoenix collapseIfZWS is not tested\n            // this.shared.collapseIfZWS();\n            this.dependencies.delete.deleteSelection();\n            selection = this.dependencies.selection.getEditableSelection();\n        }\n\n        const targetNode = selection.anchorNode;\n        const targetOffset = selection.anchorOffset;\n\n        this.insertLineBreakNode({ targetNode, targetOffset });\n        this.dependencies.history.addStep();\n    }\n\n    /**\n     * @param {Object} params\n     * @param {Node} params.targetNode\n     * @param {number} params.targetOffset\n     */\n    insertLineBreakNode({ targetNode, targetOffset }) {\n        const closestEl = closestElement(targetNode);\n        if (closestEl && !closestEl.isContentEditable) {\n            return;\n        }\n        if (targetNode.nodeType === Node.TEXT_NODE) {\n            targetOffset = splitTextNode(targetNode, targetOffset);\n            targetNode = targetNode.parentElement;\n        }\n\n        if (this.delegateTo(\"insert_line_break_element_overrides\", { targetNode, targetOffset })) {\n            return;\n        }\n\n        this.insertLineBreakElement({ targetNode, targetOffset });\n    }\n\n    /**\n     * @param {Object} params\n     * @param {HTMLElement} params.targetNode\n     * @param {number} params.targetOffset\n     */\n    insertLineBreakElement({ targetNode, targetOffset }) {\n        const closestEl = closestElement(targetNode);\n        if (closestEl && !closestEl.isContentEditable) {\n            return;\n        }\n        const restore = prepareUpdate(targetNode, targetOffset);\n\n        const brEl = this.document.createElement(\"br\");\n        const brEls = [brEl];\n        if (targetOffset >= targetNode.childNodes.length) {\n            targetNode.appendChild(brEl);\n            if (\n                !isBlock(closestElement(targetNode)) &&\n                nextLeaf(targetNode, closestBlock(targetNode))\n            ) {\n                targetNode.appendChild(this.document.createTextNode(\"\\uFEFF\"));\n            }\n        } else {\n            targetNode.insertBefore(brEl, targetNode.childNodes[targetOffset]);\n        }\n        if (\n            isFakeLineBreak(brEl) &&\n            !(getState(...leftPos(brEl), DIRECTIONS.LEFT).cType & (CTGROUPS.BLOCK | CTYPES.BR))\n        ) {\n            const brEl2 = this.document.createElement(\"br\");\n            brEl.before(brEl2);\n            brEls.unshift(brEl2);\n        }\n\n        restore();\n\n        // @todo ask AGE about why this code was only needed for unbreakable.\n        // See `this._applyCommand('oEnter') === UNBREAKABLE_ROLLBACK_CODE` in\n        // web_editor. Because now we should have a strong handling of the link\n        // selection with the link isolation, if we want to insert a BR outside,\n        // we can move the cursor outside the link.\n        // So if there is no reason to keep this code, we should remove it.\n        //\n        // const anchor = brEls[0].parentElement;\n        // // @todo @phoenix should this case be handled by a LinkPlugin?\n        // // @todo @phoenix Don't we want this for all spans ?\n        // if (anchor.nodeName === \"A\" && brEls.includes(anchor.firstChild)) {\n        //     brEls.forEach((br) => anchor.before(br));\n        //     const pos = rightPos(brEls[brEls.length - 1]);\n        //     this.dependencies.selection.setSelection({ anchorNode: pos[0], anchorOffset: pos[1] });\n        // } else if (anchor.nodeName === \"A\" && brEls.includes(anchor.lastChild)) {\n        //     brEls.forEach((br) => anchor.after(br));\n        //     const pos = rightPos(brEls[0]);\n        //     this.dependencies.selection.setSelection({ anchorNode: pos[0], anchorOffset: pos[1] });\n        // }\n        for (const el of brEls) {\n            // @todo @phoenix we don t want to setSelection multiple times\n            if (el.parentNode) {\n                const pos = rightPos(el);\n                this.dependencies.selection.setSelection({\n                    anchorNode: pos[0],\n                    anchorOffset: pos[1],\n                });\n                break;\n            }\n        }\n    }\n\n    onBeforeInput(e) {\n        if (e.inputType === \"insertLineBreak\") {\n            e.preventDefault();\n            this.insertLineBreak();\n        }\n    }\n}\n", "import { getDeepestPosition, isParagraphRelatedElement } from \"@html_editor/utils/dom_info\";\nimport { Plugin } from \"../plugin\";\nimport { isNotAllowedContent } from \"./selection_plugin\";\nimport { endPos, startPos } from \"@html_editor/utils/position\";\nimport { childNodes } from \"@html_editor/utils/dom_traversal\";\n\nexport class NoInlineRootPlugin extends Plugin {\n    static id = \"noInlineRoot\";\n    static dependencies = [\"baseContainer\", \"selection\", \"history\"];\n\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        fix_selection_on_editable_root_overrides: this.fixSelectionOnEditableRoot.bind(this),\n    };\n\n    setup() {\n        this.addDomListener(this.editable, \"keydown\", (ev) => {\n            this.currentKeyDown = ev.key;\n        });\n        this.addDomListener(this.editable, \"pointerdown\", () => {\n            this.isPointerDown = true;\n        });\n        this.addDomListener(this.editable, \"pointerup\", () => {\n            this.isPointerDown = false;\n        });\n    }\n\n    /**\n     * Places the cursor in a safe place (not the editable root).\n     * Inserts an empty paragraph if selection results from mouse click and\n     * there's no other way to insert text before/after a block.\n     *\n     * @param {import(\"./selection_plugin\").EditorSelection} selection\n     * @returns {boolean} Whether the selection was fixed\n     */\n    fixSelectionOnEditableRoot(selection) {\n        if (!selection.isCollapsed || selection.anchorNode !== this.editable) {\n            return false;\n        }\n\n        const children = childNodes(this.editable);\n        const nodeAfterCursor = children[selection.anchorOffset];\n        const nodeBeforeCursor = children[selection.anchorOffset - 1];\n        const key = this.currentKeyDown;\n        delete this.currentKeyDown;\n\n        if (key?.startsWith(\"Arrow\")) {\n            return this.fixSelectionOnEditableRootArrowKeys(nodeAfterCursor, nodeBeforeCursor, key);\n        }\n        return this.fixSelectionOnEditableRootGeneric(nodeAfterCursor, nodeBeforeCursor);\n    }\n    /**\n     * @param {Node} nodeAfterCursor\n     * @param {Node} nodeBeforeCursor\n     * @param {string} key\n     * @returns {boolean} Whether the selection was fixed\n     */\n    fixSelectionOnEditableRootArrowKeys(nodeAfterCursor, nodeBeforeCursor, key) {\n        if (![\"ArrowRight\", \"ArrowLeft\", \"ArrowUp\", \"ArrowDown\"].includes(key)) {\n            return false;\n        }\n        const directionForward = [\"ArrowRight\", \"ArrowDown\"].includes(key);\n        let node = directionForward ? nodeAfterCursor : nodeBeforeCursor;\n        while (node && isNotAllowedContent(node)) {\n            node = directionForward ? node.nextElementSibling : node.previousElementSibling;\n        }\n        if (!node) {\n            return false;\n        }\n        let [anchorNode, anchorOffset] = directionForward ? startPos(node) : endPos(node);\n        [anchorNode, anchorOffset] = getDeepestPosition(anchorNode, anchorOffset);\n        this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n        return true;\n    }\n    /**\n     * @param {Node} nodeAfterCursor\n     * @param {Node} nodeBeforeCursor\n     * @returns {boolean} Whether the selection was fixed\n     */\n    fixSelectionOnEditableRootGeneric(nodeAfterCursor, nodeBeforeCursor) {\n        if (isParagraphRelatedElement(nodeAfterCursor)) {\n            // Cursor is right before a 'P'.\n            this.dependencies.selection.setCursorStart(nodeAfterCursor);\n            return true;\n        }\n        if (isParagraphRelatedElement(nodeBeforeCursor)) {\n            // Cursor is right after a 'P'.\n            this.dependencies.selection.setCursorEnd(nodeBeforeCursor);\n            return true;\n        }\n        return false;\n    }\n}\n", "import {\n    Component,\n    onWillDestroy,\n    useEffect,\n    useExternalListener,\n    useRef,\n    useState,\n    useSubEnv,\n    xml,\n} from \"@odoo/owl\";\nimport { OVERLAY_SYMBOL } from \"@web/core/overlay/overlay_container\";\nimport { usePosition } from \"@web/core/position/position_hook\";\nimport { useActiveElement } from \"@web/core/ui/ui_service\";\n\nexport class EditorOverlay extends Component {\n    static template = xml`\n        <div t-ref=\"root\" class=\"overlay\" t-att-class=\"props.className\" t-on-pointerdown.stop=\"() => {}\">\n            <t t-component=\"props.Component\" t-props=\"props.props\"/>\n        </div>`;\n\n    static props = {\n        target: { validate: (el) => el.nodeType === Node.ELEMENT_NODE, optional: true },\n        initialSelection: { type: Object, optional: true },\n        Component: Function,\n        props: { type: Object, optional: true },\n        editable: { validate: (el) => el.nodeType === Node.ELEMENT_NODE },\n        bus: Object,\n        history: Object,\n        close: Function,\n        isOverlayOpen: Function,\n\n        // Props from createOverlay\n        positionOptions: { type: Object, optional: true },\n        className: { type: String, optional: true },\n        closeOnPointerdown: { type: Boolean, optional: true },\n        hasAutofocus: { type: Boolean, optional: true },\n    };\n\n    static defaultProps = {\n        className: \"\",\n        closeOnPointerdown: true,\n        hasAutofocus: false,\n    };\n\n    setup() {\n        this.lastSelection = this.props.initialSelection;\n        /** @type {HTMLElement} */\n        const editable = this.props.editable;\n        let getTarget, position;\n        if (this.props.target) {\n            getTarget = () => this.props.target;\n        } else {\n            this.rangeElement = editable.ownerDocument.createElement(\"range-el\");\n            editable.after(this.rangeElement);\n            onWillDestroy(() => {\n                this.rangeElement.remove();\n            });\n            getTarget = this.getSelectionTarget.bind(this);\n        }\n\n        useExternalListener(this.props.bus, \"updatePosition\", () => {\n            position.unlock();\n        });\n\n        const rootRef = useRef(\"root\");\n\n        if (this.props.positionOptions?.updatePositionOnResize ?? true) {\n            const resizeObserver = new ResizeObserver(() => {\n                position.unlock();\n            });\n            useEffect(\n                (root) => {\n                    resizeObserver.observe(root);\n                    return () => {\n                        resizeObserver.unobserve(root);\n                    };\n                },\n                () => [rootRef.el]\n            );\n        }\n\n        if (this.props.closeOnPointerdown) {\n            const clickAway = (ev) => {\n                if (!this.env[OVERLAY_SYMBOL]?.contains(ev.composedPath()[0])) {\n                    this.props.close();\n                }\n            };\n            const editableDocument = this.props.editable.ownerDocument;\n            useExternalListener(editableDocument, \"pointerdown\", clickAway);\n            // Listen to pointerdown outside the iframe\n            if (editableDocument !== document) {\n                useExternalListener(document, \"pointerdown\", clickAway);\n            }\n        }\n\n        if (this.props.hasAutofocus) {\n            useActiveElement(\"root\");\n        }\n        const topDocument = editable.ownerDocument.defaultView.top.document;\n        const scrollContainer = getScrollContainer(editable);\n        const container = scrollContainer || topDocument.documentElement;\n        const resizeObserver = new ResizeObserver(() => position.unlock());\n        resizeObserver.observe(container);\n        onWillDestroy(() => resizeObserver.disconnect());\n        const positionOptions = {\n            position: \"bottom-start\",\n            container: container,\n            ...this.props.positionOptions,\n            onPositioned: (el, solution) => {\n                this.props.positionOptions?.onPositioned?.(el, solution);\n                this.updateVisibility(el, solution, scrollContainer);\n            },\n        };\n        position = usePosition(\"root\", getTarget, positionOptions);\n\n        this.overlayState = useState({ isOverlayVisible: true });\n        useSubEnv({ overlayState: this.overlayState });\n    }\n\n    getSelectionTarget() {\n        const doc = this.props.editable.ownerDocument;\n        const selection = doc.getSelection();\n        if (!selection || !selection.rangeCount || !this.props.isOverlayOpen()) {\n            return null;\n        }\n        const inEditable = this.props.editable.contains(selection.anchorNode);\n        let range;\n        if (inEditable) {\n            range = selection.getRangeAt(0);\n            this.lastSelection = { range };\n        } else {\n            if (!this.lastSelection) {\n                return null;\n            }\n            range = this.lastSelection.range;\n        }\n        let rect = range.getBoundingClientRect();\n        if (rect.x === 0 && rect.width === 0 && rect.height === 0) {\n            // Attention, ignoring DOM mutations is always dangerous (when we add or remove nodes)\n            // because if another mutation uses the target that is not observed, that mutation can never be applied\n            // again (when undo/redo and in collaboration).\n            this.props.history.ignoreDOMMutations(() => {\n                const clonedRange = range.cloneRange();\n                const shadowCaret = doc.createTextNode(\"|\");\n                clonedRange.insertNode(shadowCaret);\n                clonedRange.selectNode(shadowCaret);\n                rect = clonedRange.getBoundingClientRect();\n                shadowCaret.remove();\n                clonedRange.detach();\n            });\n        }\n        // Html element with a patched getBoundingClientRect method. It\n        // represents the range as a (HTMLElement) target for the usePosition\n        // hook.\n        this.rangeElement.getBoundingClientRect = () => rect;\n        return this.rangeElement;\n    }\n\n    updateVisibility(overlayElement, solution, scrollContainer) {\n        // @todo: mobile tests rely on a visible (yet overflowing) toolbar\n        // Remove this once the mobile toolbar is fixed?\n        if (this.env.isSmall) {\n            return;\n        }\n        const shouldBeVisible = this.shouldOverlayBeVisible(\n            overlayElement,\n            solution,\n            scrollContainer\n        );\n        overlayElement.style.visibility = shouldBeVisible ? \"visible\" : \"hidden\";\n        this.overlayState.isOverlayVisible = shouldBeVisible;\n    }\n\n    /**\n     * @param {HTMLElement} overlayElement\n     * @param {Object} solution\n     * @param {HTMLElement} scrollContainer\n     */\n    shouldOverlayBeVisible(overlayElement, solution, scrollContainer) {\n        if (!scrollContainer) {\n            return true;\n        }\n        const scrollContainerRect = scrollContainer.getBoundingClientRect();\n        const top = Math.max(scrollContainerRect.top, 0);\n        const bottom = top + scrollContainerRect.height;\n        const overflowsTop = solution.top < top;\n        const overflowsBottom = solution.top + overlayElement.offsetHeight > bottom;\n        const canFlip = this.props.positionOptions?.flip ?? true;\n        if (overflowsTop) {\n            if (overflowsBottom) {\n                // Overlay is bigger than the cointainer. Hiding it would make\n                // it always invisible.\n                return true;\n            }\n            if (solution.direction === \"top\" && canFlip) {\n                // Scrolling down will make overlay eventually flip and no longer overflow\n                return true;\n            }\n            return false;\n        }\n        if (overflowsBottom) {\n            if (solution.direction === \"bottom\" && canFlip) {\n                // Scrolling up will make overlay eventually flip and no longer overflow\n                return true;\n            }\n            return false;\n        }\n        return true;\n    }\n}\n\n/**\n * The scroll container is an ancestor of {@link el} that is:\n * - scrollable and\n * - not also ancestor of a fixed element encosing `el` in the same\n * document (as this makes `el` fixed and not affected by scrolls of\n * that ancestor)\n *\n * @param {HTMLElement} el\n * @returns {HTMLElement|null}\n */\nexport function getScrollContainer(el) {\n    const isScrollable = (/** @type {HTMLElement} */ el) => {\n        if (el.tagName === \"HTML\") {\n            return el.scrollHeight > el.ownerDocument.defaultView.visualViewport.height;\n        }\n        return (\n            el.scrollHeight > el.clientHeight &&\n            /\\bauto\\b|\\bscroll\\b/.test(getComputedStyle(el)[\"overflow-y\"])\n        );\n    };\n    const isFixed = (el) => getComputedStyle(el).position === \"fixed\";\n    while (el) {\n        if (isScrollable(el)) {\n            return el;\n        }\n        if (isFixed(el)) {\n            // Any scrollable ancestor in the same document does not affect it.\n            // Search in the enclosing document, if any.\n            el = el.ownerDocument.defaultView.frameElement;\n            continue;\n        }\n        el = el.parentElement || el.ownerDocument.defaultView.frameElement;\n    }\n    return null;\n}\n", "import { markRaw, EventBus } from \"@odoo/owl\";\nimport { Plugin } from \"../plugin\";\nimport { EditorOverlay } from \"./overlay\";\n\n/**\n * @typedef { Object } OverlayShared\n * @property { OverlayPlugin['createOverlay'] } createOverlay\n */\n\n/**\n * Provides the following feature:\n * - adding a component in overlay above the editor, with proper positioning\n */\nexport class OverlayPlugin extends Plugin {\n    static id = \"overlay\";\n    static dependencies = [\"history\"];\n    static shared = [\"createOverlay\"];\n\n    overlays = [];\n\n    destroy() {\n        super.destroy();\n        for (const overlay of this.overlays) {\n            overlay.close();\n        }\n    }\n\n    /**\n     * Creates an overlay component and adds it to the list of overlays.\n     *\n     * @param {Function} Component\n     * @param {Object} [props={}]\n     * @param {Object} [options]\n     * @returns {Overlay}\n     */\n    createOverlay(Component, props = {}, options) {\n        const overlay = new Overlay(this, Component, props, options);\n        this.overlays.push(overlay);\n        return overlay;\n    }\n}\n\nexport class Overlay {\n    constructor(plugin, C, props, options) {\n        this.plugin = plugin;\n        this.C = C;\n        this.editorOverlayProps = props;\n        this.options = options;\n        this.isOpen = false;\n        this._remove = null;\n        this.component = null;\n        this.bus = new EventBus();\n    }\n\n    /**\n     * @param {Object} options\n     * @param {HTMLElement | null} [options.target] for the overlay.\n     *  If null or undefined, the current selection will be used instead\n     * @param {any} [options.props] overlay component props\n     */\n    open({ target, props }) {\n        if (this.isOpen) {\n            this.updatePosition();\n        } else {\n            this.isOpen = true;\n            const selection = this.plugin.editable.ownerDocument.getSelection();\n            let initialSelection;\n            if (selection && selection.type !== \"None\") {\n                initialSelection = {\n                    range: selection.getRangeAt(0),\n                };\n            }\n            this._remove = this.plugin.services.overlay.add(\n                EditorOverlay,\n                markRaw({\n                    ...this.editorOverlayProps,\n                    Component: this.C,\n                    editable: this.plugin.editable,\n                    props,\n                    target,\n                    initialSelection,\n                    bus: this.bus,\n                    close: this.close.bind(this),\n                    isOverlayOpen: this.isOverlayOpen.bind(this),\n                    history: {\n                        ignoreDOMMutations: this.plugin.dependencies.history.ignoreDOMMutations,\n                    },\n                }),\n                {\n                    ...this.options,\n                }\n            );\n        }\n    }\n\n    close() {\n        this.isOpen = false;\n        if (this._remove) {\n            this._remove();\n        }\n    }\n\n    isOverlayOpen() {\n        return this.isOpen;\n    }\n\n    updatePosition() {\n        this.bus.trigger(\"updatePosition\");\n    }\n}\n", "import { Plugin } from \"../plugin\";\nimport { isProtecting, isUnprotecting } from \"../utils/dom_info\";\nimport { childNodes } from \"../utils/dom_traversal\";\nimport { withSequence } from \"@html_editor/utils/resource\";\n\nconst PROTECTED_SELECTOR = `[data-oe-protected=\"true\"],[data-oe-protected=\"\"]`;\nconst UNPROTECTED_SELECTOR = `[data-oe-protected=\"false\"]`;\n\n/**\n * @typedef { Object } ProtectedNodeShared\n * @property { ProtectedNodePlugin['setProtectingNode'] } setProtectingNode\n *\n * @typedef { import(\"./history_plugin\").HistoryMutationRecord } HistoryMutationRecord\n */\n\nexport class ProtectedNodePlugin extends Plugin {\n    static id = \"protectedNode\";\n    static shared = [\"setProtectingNode\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        clean_for_save_handlers: ({ root }) => this.cleanForSave(root),\n        normalize_handlers: withSequence(0, this.normalize.bind(this)),\n        before_filter_mutation_record_handlers: this.beforeFilteringMutationRecords.bind(this),\n\n        unsplittable_node_predicates: [\n            isProtecting, // avoid merge\n            isUnprotecting,\n        ],\n        savable_mutation_record_predicates: this.isMutationRecordSavable.bind(this),\n        removable_descendants_providers: this.filterDescendantsToRemove.bind(this),\n    };\n\n    setup() {\n        this.protectedNodes = new WeakSet();\n    }\n\n    filterDescendantsToRemove(elem) {\n        // TODO @phoenix: history plugin can register protected nodes in its\n        // id maps, should it be prevented? => if yes, take care that data-oe-protected=\"false\"\n        // elements should also be registered even though they are protected.\n        if (isProtecting(elem)) {\n            const descendantsToRemove = [];\n            for (const candidate of elem.querySelectorAll(UNPROTECTED_SELECTOR)) {\n                if (candidate.closest(PROTECTED_SELECTOR) === elem) {\n                    descendantsToRemove.push(...childNodes(candidate));\n                }\n            }\n            return descendantsToRemove;\n        }\n    }\n\n    protectNode(node) {\n        if (node.nodeType === Node.ELEMENT_NODE) {\n            if (node.matches(UNPROTECTED_SELECTOR)) {\n                this.unProtectDescendants(node);\n            } else if (!this.protectedNodes.has(node)) {\n                this.protectDescendants(node);\n            }\n            // assume that descendants are already handled if the node\n            // is already protected.\n        }\n        this.protectedNodes.add(node);\n    }\n\n    unProtectNode(node) {\n        if (node.nodeType === Node.ELEMENT_NODE) {\n            if (node.matches(PROTECTED_SELECTOR)) {\n                this.protectDescendants(node);\n            } else if (this.protectedNodes.has(node)) {\n                this.unProtectDescendants(node);\n            }\n            // assume that descendants are already handled if the node\n            // is already not protected.\n        }\n        this.protectedNodes.delete(node);\n    }\n\n    protectDescendants(node) {\n        let child = node.firstChild;\n        while (child) {\n            this.protectNode(child);\n            child = child.nextSibling;\n        }\n    }\n\n    unProtectDescendants(node) {\n        let child = node.firstChild;\n        while (child) {\n            this.unProtectNode(child);\n            child = child.nextSibling;\n        }\n    }\n\n    /**\n     * @param {HistoryMutationRecord[]} records\n     */\n    beforeFilteringMutationRecords(records) {\n        for (const record of records) {\n            if (record.type === \"childList\") {\n                if (record.target.nodeType !== Node.ELEMENT_NODE) {\n                    return;\n                }\n                const addedNodes = record.addedTrees.map((tree) => tree.node);\n                if (\n                    (this.protectedNodes.has(record.target) &&\n                        !record.target.matches(UNPROTECTED_SELECTOR)) ||\n                    record.target.matches(PROTECTED_SELECTOR)\n                ) {\n                    for (const addedNode of addedNodes) {\n                        this.protectNode(addedNode);\n                    }\n                } else if (\n                    !this.protectedNodes.has(record.target) ||\n                    record.target.matches(UNPROTECTED_SELECTOR)\n                ) {\n                    for (const addedNode of addedNodes) {\n                        this.unProtectNode(addedNode);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * @param {HistoryMutationRecord} record\n     * @return {boolean}\n     */\n    isMutationRecordSavable(record) {\n        if (record.type === \"childList\") {\n            return !(\n                (this.protectedNodes.has(record.target) &&\n                    !record.target.matches(UNPROTECTED_SELECTOR)) ||\n                record.target.matches(PROTECTED_SELECTOR)\n            );\n        }\n        return !this.protectedNodes.has(record.target);\n    }\n\n    forEachProtectingElem(elem, callback) {\n        const selector = `[data-oe-protected]`;\n        const protectingNodes = [...elem.querySelectorAll(selector)].reverse();\n        if (elem.matches(selector)) {\n            protectingNodes.push(elem);\n        }\n        for (const protectingNode of protectingNodes) {\n            if (protectingNode.dataset.oeProtected === \"false\") {\n                callback(protectingNode, false);\n            } else {\n                callback(protectingNode, true);\n            }\n        }\n    }\n\n    normalize(elem) {\n        this.forEachProtectingElem(elem, this.setProtectingNode.bind(this));\n    }\n\n    setProtectingNode(elem, protecting) {\n        elem.dataset.oeProtected = protecting;\n        // contenteditable attribute is set on (un)protecting nodes for\n        // implementation convenience. This could be removed but the editor\n        // should be adapted to handle some use cases that are handled for\n        // contenteditable elements. Currently unsupported configurations:\n        // 1) unprotected non-editable content: would typically be added/removed\n        // programmatically and shared in collaboration => some logic should\n        // be added to handle undo/redo properly for consistency.\n        // -> A adds content, A replaces his content with a new one, B replaces\n        //   content of A with his own, A undo => there is now the content of B\n        //   and the old content of A in the node, is it still coherent?\n        // 2) protected editable content: need a specification of which\n        // functions of the editor are allowed to work (and how) in that\n        // editable part (none?) => should be enforced.\n        if (protecting) {\n            elem.setAttribute(\"contenteditable\", \"false\");\n            this.protectDescendants(elem);\n        } else {\n            elem.setAttribute(\"contenteditable\", \"true\");\n            this.unProtectDescendants(elem);\n        }\n    }\n\n    cleanForSave(clone) {\n        this.forEachProtectingElem(clone, (protectingNode) => {\n            protectingNode.removeAttribute(\"contenteditable\");\n        });\n    }\n}\n", "import { selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { Plugin } from \"../plugin\";\n\n/**\n * @typedef { Object } SanitizeShared\n * @property { SanitizePlugin['sanitize'] } sanitize\n */\n\nexport class SanitizePlugin extends Plugin {\n    static id = \"sanitize\";\n    static shared = [\"sanitize\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        normalize_handlers: this.normalize.bind(this),\n    };\n\n    setup() {\n        if (!window.DOMPurify) {\n            throw new Error(\"DOMPurify is not available\");\n        }\n        this.DOMPurify = DOMPurify(this.window);\n    }\n    /**\n     * Sanitizes in place an html element. Current implementation uses the\n     * DOMPurify library.\n     *\n     * @param {HTMLElement} elem\n     * @returns {HTMLElement} the element itself\n     */\n    sanitize(elem) {\n        return this.DOMPurify.sanitize(elem, {\n            IN_PLACE: true,\n            ADD_TAGS: [\"#document-fragment\", \"fake-el\"],\n            ADD_ATTR: [\"contenteditable\", \"t-field\", \"t-out\", \"t-esc\"],\n        });\n    }\n\n    normalize(element) {\n        for (const el of selectElements(\n            element,\n            \".o-contenteditable-false, .o-contenteditable-true\"\n        )) {\n            el.contentEditable = el.matches(\".o-contenteditable-true\");\n        }\n        for (const el of selectElements(element, \"[data-oe-role]\")) {\n            el.setAttribute(\"role\", el.dataset.oeRole);\n        }\n        for (const el of selectElements(element, \"[data-oe-aria-label]\")) {\n            el.setAttribute(\"aria-label\", el.dataset.oeAriaLabel);\n        }\n    }\n\n    /**\n     * Ensure that attributes sanitized by the server are properly removed before\n     * the save, to avoid mismatches and a reset of the editable content.\n     * Only attributes under the responsibility (associated with an editor\n     * attribute or class) of the sanitize plugin are removed.\n     *\n     * /!\\ CAUTION: using server-sanitized attributes without editor-specific\n     * classes/attributes in a custom plugin should be managed by that same\n     * custom plugin.\n     */\n    cleanForSave({ root }) {\n        for (const el of selectElements(\n            root,\n            \".o-contenteditable-false, .o-contenteditable-true\"\n        )) {\n            el.removeAttribute(\"contenteditable\");\n        }\n        for (const el of selectElements(root, \"[data-oe-role]\")) {\n            el.removeAttribute(\"role\");\n        }\n        for (const el of selectElements(root, \"[data-oe-aria-label]\")) {\n            el.removeAttribute(\"aria-label\");\n        }\n    }\n}\n", "import { closestBlock } from \"@html_editor/utils/blocks\";\nimport {\n    getDeepestPosition,\n    isMediaElement,\n    isProtected,\n    isProtecting,\n    isUnprotecting,\n} from \"@html_editor/utils/dom_info\";\nimport {\n    childNodes,\n    closestElement,\n    descendants,\n    firstLeaf,\n    lastLeaf,\n} from \"@html_editor/utils/dom_traversal\";\nimport { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\nimport { Plugin } from \"../plugin\";\nimport { DIRECTIONS, leftPos, nodeSize, rightPos } from \"../utils/position\";\nimport {\n    getAdjacentCharacter,\n    normalizeDeepCursorPosition,\n    normalizeFakeBR,\n    normalizeNotEditableNode,\n    normalizeSelfClosingElement,\n} from \"../utils/selection\";\nimport { closestScrollableY } from \"@web/core/utils/scrolling\";\nimport { weakMemoize } from \"@html_editor/utils/functions\";\n\n/**\n * @typedef { Object } EditorSelection\n * @property { Node } anchorNode\n * @property { number } anchorOffset\n * @property { Node } focusNode\n * @property { number } focusOffset\n * @property { Node } startContainer\n * @property { number } startOffset\n * @property { Node } endContainer\n * @property { number } endOffset\n * @property { Node } commonAncestorContainer\n * @property { boolean } isCollapsed\n * @property { boolean } direction\n * @property { () => string } textContent\n * @property { (node: Node) => boolean } intersectsNode\n */\n\n/**\n * @typedef {Object} SelectionData\n * @property {EditorSelection} documentSelection\n * @property {EditorSelection} editableSelection\n * @property {EditorSelection} deepEditableSelection\n * @property { boolean } documentSelectionIsInEditable\n * @property { boolean } documentSelectionIsProtected\n * @property { boolean } documentSelectionIsProtecting\n * @property { boolean } currentSelectionIsInEditable\n */\n\n/**\n * @typedef {Object} Cursors\n * @property {() => void} restore\n * @property {(callback: (cursor: Cursor) => void) => Cursors} update\n * @property {(node: Node, newNode: Node) => Cursors} remapNode\n * @property {(node: Node, newOffset: number) => Cursors} setOffset\n * @property {(node: Node, shiftOffset: number) => Cursors} shiftOffset\n */\n\n/**\n * @typedef {Object} Cursor\n * @property {Node} node\n * @property {number} offset\n */\n\n// https://developer.mozilla.org/en-US/docs/Glossary/Void_element\nconst VOID_ELEMENT_NAMES = [\n    \"AREA\",\n    \"BASE\",\n    \"BR\",\n    \"COL\",\n    \"EMBED\",\n    \"HR\",\n    \"IMG\",\n    \"INPUT\",\n    \"KEYGEN\",\n    \"LINK\",\n    \"META\",\n    \"PARAM\",\n    \"SOURCE\",\n    \"TRACK\",\n    \"WBR\",\n];\n\nexport function isArtificialVoidElement(node) {\n    return isMediaElement(node) || node.nodeName === \"HR\";\n}\n\nexport function isNotAllowedContent(node) {\n    return isArtificialVoidElement(node) || VOID_ELEMENT_NAMES.includes(node.nodeName);\n}\n\nexport const isHtmlContentSupported = weakMemoize(\n    (/** @type {EditorSelection} */ selection) =>\n        !closestElement(\n            selection.focusNode,\n            '[data-oe-model]:not([data-oe-type=\"html\"]):not([data-oe-field=\"arch\"]):not([data-oe-translation-source-sha])'\n        )\n);\n\n/**\n * @returns edge text nodes if they do not have content selected\n */\nfunction getUnselectedEdgeTextNodes(selection) {\n    const startEdgeNodes = (node, offset) =>\n        node === selection.commonAncestorContainer || offset < nodeSize(node)\n            ? []\n            : [node, ...startEdgeNodes(...rightPos(node))];\n    const endEdgeNodes = (node, offset) =>\n        node === selection.commonAncestorContainer || offset > 0\n            ? []\n            : [node, ...endEdgeNodes(...leftPos(node))];\n    return new Set(\n        [\n            ...startEdgeNodes(selection.startContainer, selection.startOffset),\n            ...endEdgeNodes(selection.endContainer, selection.endOffset),\n        ].filter((node) => node.nodeType === Node.TEXT_NODE)\n    );\n}\n\n/**\n * Scrolls the view to a specific node's position in the document\n * @param {Selection} selection - The current document selection\n * @returns {void}\n */\nfunction scrollToSelection(selection) {\n    const range = selection.getRangeAt(0);\n    const container = closestScrollableY(range.startContainer.parentElement);\n    if (!container) {\n        // If the container is not scrollable we don't scroll\n        return;\n    }\n    let rect = range.getBoundingClientRect();\n    // If the range is invisible (0 width & height),\n    // We call `getBoundingClientRect` on closest element.\n    if (rect.width === 0 && rect.height === 0 && selection.isCollapsed) {\n        rect = closestElement(selection.anchorNode).getBoundingClientRect();\n    }\n\n    const containerRect = container.getBoundingClientRect();\n    const offsetTop = rect.top - containerRect.top + container.scrollTop;\n    const offsetBottom = rect.bottom - containerRect.top + container.scrollTop;\n\n    if (rect.bottom > containerRect.top && rect.top < containerRect.bottom) {\n        // If selection is partially visible, no need to scroll.\n        return;\n    }\n    // Simulate the \"nearest\" behavior by scrolling to the closest top/bottom edge\n    if (rect.top < containerRect.top) {\n        container.scrollTo({ top: offsetTop, behavior: \"instant\" });\n    } else if (rect.bottom > containerRect.bottom) {\n        container.scrollTo({ top: offsetBottom - container.clientHeight, behavior: \"instant\" });\n    }\n}\n\n/**\n * @typedef { Object } SelectionShared\n * @property { SelectionPlugin['extractContent'] } extractContent\n * @property { SelectionPlugin['focusEditable'] } focusEditable\n * @property { SelectionPlugin['getEditableSelection'] } getEditableSelection\n * @property { SelectionPlugin['getSelectionData'] } getSelectionData\n * @property { SelectionPlugin['getTargetedBlocks'] } getTargetedBlocks\n * @property { SelectionPlugin['getTargetedNodes'] } getTargetedNodes\n * @property { SelectionPlugin['modifySelection'] } modifySelection\n * @property { SelectionPlugin['preserveSelection'] } preserveSelection\n * @property { SelectionPlugin['rectifySelection'] } rectifySelection\n * @property { SelectionPlugin['areNodeContentsFullySelected'] } areNodeContentsFullySelected\n * @property { SelectionPlugin['resetSelection'] } resetSelection\n * @property { SelectionPlugin['setCursorEnd'] } setCursorEnd\n * @property { SelectionPlugin['setCursorStart'] } setCursorStart\n * @property { SelectionPlugin['setSelection'] } setSelection\n * @property { SelectionPlugin['isSelectionInEditable'] } isSelectionInEditable\n * @property { SelectionPlugin['isNodeEditable'] } isNodeEditable\n * @property { SelectionPlugin['selectAroundNonEditable'] } selectAroundNonEditable\n */\n\n/**\n * @typedef {((selectionData: SelectionData) => void)[]} selectionchange_handlers\n * @typedef {(() => void)[]} selection_leave_handlers\n *\n * @typedef {((ev: PointerEvent) => void | true)[]} double_click_overrides\n * @typedef {((ev: PointerEvent) => void | true)[]} triple_click_overrides\n * @typedef {((selection: EditorSelection) => boolean)[]} fix_selection_on_editable_root_overrides\n *\n * @typedef {((node: Node, selection: EditorSelection, range: Range) => boolean)[]} fully_selected_node_predicates\n * @typedef {((ev: Event, char: string, lastSkipped: string) => boolean)[]} intangible_char_for_keyboard_navigation_predicates\n * @typedef {((node: Node) => boolean)[]} is_node_editable_predicates\n *\n * @typedef {((targetedNodes: Node[]) => Node[])[]} targeted_nodes_processors\n */\n\nexport class SelectionPlugin extends Plugin {\n    static id = \"selection\";\n    static shared = [\n        \"getSelectionData\",\n        \"getEditableSelection\",\n        \"setSelection\",\n        \"setCursorStart\",\n        \"setCursorEnd\",\n        \"extractContent\",\n        \"preserveSelection\",\n        \"resetSelection\",\n        \"getTargetedNodes\",\n        \"getTargetedBlocks\",\n        \"modifySelection\",\n        \"rectifySelection\",\n        \"areNodeContentsFullySelected\",\n        \"focusEditable\",\n        // \"collapseIfZWS\",\n        \"isSelectionInEditable\",\n        \"isNodeEditable\",\n        \"selectAroundNonEditable\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: { id: \"selectAll\", run: this.selectAll.bind(this) },\n        shortcuts: [{ hotkey: \"control+a\", commandId: \"selectAll\" }],\n    };\n\n    setup() {\n        this.resetSelection();\n        this.addGlobalDomListener(\"selectionchange\", () => {\n            this.updateActiveSelection();\n            const selection = this.document.getSelection();\n            if (this.isSelectionInEditable(selection)) {\n                scrollToSelection(selection);\n            }\n        });\n        this.addDomListener(this.editable, \"mousedown\", (ev) => {\n            if (ev.detail && ev.detail % 3 === 2) {\n                this.onDoubleClick(ev);\n            }\n            if (ev.detail && ev.detail % 3 === 0) {\n                this.onTripleClick(ev);\n            }\n        });\n        this.addDomListener(this.editable, \"keydown\", (ev) => {\n            const handled = [\n                \"arrowright\",\n                \"shift+arrowright\",\n                \"arrowleft\",\n                \"shift+arrowleft\",\n                \"shift+arrowup\",\n                \"shift+arrowdown\",\n            ];\n            if (handled.includes(getActiveHotkey(ev))) {\n                this.onKeyDownArrows(ev);\n            }\n        });\n\n        this.focusEditableDocument = true;\n        if (this.document !== document) {\n            const focusEditable = () => {\n                this.focusEditableDocument = true;\n            };\n            const unFocusEditable = (ev) => {\n                if (this.focusEditableDocument) {\n                    // autofocus trigger when you close a popover (like color picker)\n                    if (ev.target.tagName === \"IFRAME\") {\n                        return;\n                    }\n                    const preventClosing = ev.target?.closest?.(\"[data-prevent-closing-overlay]\");\n                    if (preventClosing?.dataset?.preventClosingOverlay === \"true\") {\n                        return;\n                    }\n                    this.focusEditableDocument = false;\n                    this.dispatchTo(\"selection_leave_handlers\");\n                }\n            };\n            this.addDomListener(this.document, \"focusin\", focusEditable, { capture: true });\n            this.addDomListener(document, \"focusin\", unFocusEditable, { capture: true });\n            this.addDomListener(this.document, \"pointerdown\", focusEditable, { capture: true });\n            this.addDomListener(document, \"pointerdown\", unFocusEditable, { capture: true });\n        }\n    }\n\n    selectAll() {\n        const selection = this.getEditableSelection();\n        const containerSelector = \"#wrap > *, .oe_structure > *, [contenteditable]\";\n        const container = selection && closestElement(selection.anchorNode, containerSelector);\n        const [anchorNode, anchorOffset] = getDeepestPosition(container, 0);\n        const [focusNode, focusOffset] = getDeepestPosition(container, nodeSize(container));\n        if (\n            this.delegateTo(\"select_all_overrides\", {\n                anchorNode,\n                anchorOffset,\n                focusNode,\n                focusOffset,\n            })\n        ) {\n            return;\n        }\n        this.setSelection({ anchorNode, anchorOffset, focusNode, focusOffset });\n    }\n\n    resetSelection() {\n        this.activeSelection = this.makeActiveSelection();\n    }\n\n    onDoubleClick(ev) {\n        const selectionData = this.getSelectionData();\n        if (selectionData.documentSelectionIsInEditable) {\n            if (this.delegateTo(\"double_click_overrides\", ev)) {\n                // If the override is handled, we don't do anything.\n                return;\n            }\n        }\n    }\n\n    onTripleClick(ev) {\n        const selectionData = this.getSelectionData();\n        if (selectionData.documentSelectionIsInEditable) {\n            if (this.delegateTo(\"triple_click_overrides\", ev)) {\n                // If the override is handled, we don't do anything.\n                return;\n            }\n            const { documentSelection } = selectionData;\n            const block = closestBlock(documentSelection.anchorNode);\n            const [anchorNode, anchorOffset] = getDeepestPosition(block, 0);\n            const [focusNode, focusOffset] = getDeepestPosition(block, nodeSize(block));\n            this.setSelection({ anchorNode, anchorOffset, focusNode, focusOffset });\n            ev.preventDefault();\n            return;\n        }\n    }\n\n    /**\n     * Update the active selection to the current selection in the editor.\n     */\n    updateActiveSelection() {\n        this.previousActiveSelection = this.activeSelection;\n        // getSelectionData sets this.activeSelection to the current selection\n        const selectionData = this.getSelectionData();\n        if (this.fixSelectionOnEditableRoot(selectionData)) {\n            return;\n        }\n        this.dispatchTo(\"selectionchange_handlers\", selectionData);\n    }\n\n    /**\n     * @param { Selection } [selection] The DOM selection\n     * @return { EditorSelection }\n     */\n    makeActiveSelection(selection) {\n        let range;\n        let activeSelection;\n        if (!selection || !selection.rangeCount) {\n            const [targetNode, targetOffset] = this.config.allowInlineAtRoot\n                ? [this.editable, 0]\n                : getDeepestPosition(this.editable, 0);\n            activeSelection = {\n                anchorNode: targetNode,\n                anchorOffset: targetOffset,\n                focusNode: targetNode,\n                focusOffset: targetOffset,\n                startContainer: targetNode,\n                startOffset: targetOffset,\n                endContainer: targetNode,\n                endOffset: targetOffset,\n                commonAncestorContainer: targetNode,\n                isCollapsed: true,\n                direction: DIRECTIONS.RIGHT,\n                textContent: () => \"\",\n                intersectsNode: () => false,\n            };\n        } else {\n            range = selection.getRangeAt(0);\n            let { anchorNode, anchorOffset, focusNode, focusOffset } = selection;\n            let direction =\n                anchorNode === range.startContainer ? DIRECTIONS.RIGHT : DIRECTIONS.LEFT;\n            if (anchorNode === focusNode && focusOffset < anchorOffset) {\n                direction = !direction;\n            }\n            if (\n                this.activeSelection &&\n                (isProtecting(anchorNode) ||\n                    (isProtected(anchorNode) && !isUnprotecting(anchorNode)))\n            ) {\n                // Keep the previous activeSelection in case of user interactions\n                // inside a protected zone.\n                return this.activeSelection;\n            }\n            [anchorNode, anchorOffset] = normalizeSelfClosingElement(anchorNode, anchorOffset);\n            [focusNode, focusOffset] = normalizeSelfClosingElement(focusNode, focusOffset);\n            const [startContainer, startOffset, endContainer, endOffset] =\n                direction === DIRECTIONS.RIGHT\n                    ? [anchorNode, anchorOffset, focusNode, focusOffset]\n                    : [focusNode, focusOffset, anchorNode, anchorOffset];\n            range = this.document.createRange();\n            range.setStart(startContainer, startOffset);\n            range.setEnd(endContainer, endOffset);\n\n            activeSelection = {\n                anchorNode,\n                anchorOffset,\n                focusNode,\n                focusOffset,\n                startContainer,\n                startOffset,\n                endContainer,\n                endOffset,\n                commonAncestorContainer: range.commonAncestorContainer,\n                isCollapsed: range.collapsed,\n                direction,\n                textContent: () => (range.collapsed ? \"\" : selection.toString()),\n                intersectsNode: (node) => range.intersectsNode(node),\n            };\n        }\n\n        Object.freeze(activeSelection);\n        return activeSelection;\n    }\n\n    /**\n     * @param { EditorSelection } selection\n     */\n    extractContent(selection) {\n        const range = new Range();\n        range.setStart(selection.startContainer, selection.startOffset);\n        range.setEnd(selection.endContainer, selection.endOffset);\n        this.setSelection({\n            anchorNode: selection.startContainer,\n            anchorOffset: selection.startOffset,\n        });\n        return range.extractContents();\n    }\n\n    /**\n     * @param { Node } anchorNode\n     * @param { number } anchorOffset\n     * @param { Node } focusNode\n     * @param { number } focusOffset\n     * @param { boolean } direction\n     *\n     * @return { EditorSelection }\n     */\n    createEditorSelection(anchorNode, anchorOffset, focusNode, focusOffset, direction) {\n        let startContainer, startOffset, endContainer, endOffset;\n        const range = new Range();\n        if (direction) {\n            [startContainer, startOffset] = [anchorNode, anchorOffset];\n            [endContainer, endOffset] = [focusNode, focusOffset];\n        } else {\n            [startContainer, startOffset] = [focusNode, focusOffset];\n            [endContainer, endOffset] = [anchorNode, anchorOffset];\n        }\n\n        range.setStart(startContainer, startOffset);\n        range.setEnd(endContainer, endOffset);\n        return Object.freeze({\n            ...this.activeSelection,\n            anchorNode,\n            anchorOffset,\n            focusNode,\n            focusOffset,\n            startContainer,\n            startOffset,\n            endContainer,\n            endOffset,\n            commonAncestorContainer: range.commonAncestorContainer,\n            cloneContents: () => range.cloneContents(),\n        });\n    }\n    /**\n     @return { EditorSelection }\n     */\n    getEditableSelection() {\n        return this.getSelectionData().editableSelection;\n    }\n\n    /**\n     * @return { SelectionData }\n     */\n    getSelectionData() {\n        const selection = this.document.getSelection();\n        const documentSelectionIsInEditable = selection && this.isSelectionInEditable(selection);\n        const documentSelection =\n            selection?.anchorNode && selection?.focusNode\n                ? Object.freeze({\n                      get isCollapsed() {\n                          if (this.collapsed === undefined) {\n                              this.collapsed = selection.isCollapsed;\n                          }\n                          return this.collapsed;\n                      },\n                      anchorNode: selection.anchorNode,\n                      anchorOffset: selection.anchorOffset,\n                      focusNode: selection.focusNode,\n                      focusOffset: selection.focusOffset,\n                      commonAncestorContainer: selection.rangeCount\n                          ? selection.getRangeAt(0).commonAncestorContainer\n                          : null,\n                  })\n                : null;\n        if (documentSelectionIsInEditable) {\n            this.activeSelection = this.makeActiveSelection(selection);\n        } else if (!this.activeSelection.anchorNode.isConnected) {\n            this.activeSelection = this.makeActiveSelection();\n        }\n        let { anchorNode, anchorOffset, focusNode, focusOffset, isCollapsed, direction } =\n            this.activeSelection;\n\n        const editableSelection = this.createEditorSelection(\n            anchorNode,\n            anchorOffset,\n            focusNode,\n            focusOffset,\n            direction\n        );\n\n        const selectionData = {\n            documentSelection: documentSelection,\n            editableSelection: editableSelection,\n            documentSelectionIsInEditable: documentSelectionIsInEditable,\n            currentSelectionIsInEditable:\n                documentSelectionIsInEditable && this.focusEditableDocument,\n        };\n\n        Object.defineProperty(selectionData, \"deepEditableSelection\", {\n            get: function () {\n                // Transform the selection to return the depest possible node.\n                [anchorNode, anchorOffset] = getDeepestPosition(anchorNode, anchorOffset);\n                [focusNode, focusOffset] = isCollapsed\n                    ? [anchorNode, anchorOffset]\n                    : getDeepestPosition(focusNode, focusOffset);\n                return this.createEditorSelection(\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                    direction\n                );\n            }.bind(this),\n        });\n\n        Object.defineProperty(selectionData, \"documentSelectionIsProtecting\", {\n            get: function () {\n                return documentSelection?.anchorNode\n                    ? isProtecting(documentSelection.anchorNode)\n                    : false;\n            }.bind(this),\n        });\n        Object.defineProperty(selectionData, \"documentSelectionIsProtected\", {\n            get: function () {\n                return documentSelection?.anchorNode\n                    ? isProtected(documentSelection.anchorNode)\n                    : false;\n            }.bind(this),\n        });\n\n        return Object.freeze(selectionData);\n    }\n\n    /**\n     * Returns true if selection is valid and in the editable.\n     * Otherwise, returns false and logs a warning.\n     */\n    validateSelection({ anchorNode, anchorOffset, focusNode, focusOffset }) {\n        const validateNode = (node) => {\n            if (!this.editable.contains(node)) {\n                console.warn(\"Invalid selection. Node is not part of the editable:\", node);\n                return false;\n            }\n            return true;\n        };\n        const validateOffset = (node, offset) => {\n            if (offset < 0 || offset > nodeSize(node)) {\n                console.warn(\"Invalid selection. Offset is out of bounds:\", offset, node);\n                return false;\n            }\n            return true;\n        };\n        const isCollapsed = anchorNode === focusNode && anchorOffset === focusOffset;\n        return (\n            validateNode(anchorNode) &&\n            (focusNode === anchorNode || validateNode(focusNode)) &&\n            validateOffset(anchorNode, anchorOffset) &&\n            (isCollapsed || validateOffset(focusNode, focusOffset))\n        );\n    }\n\n    /**\n     * Set the selection in the editor.\n     *\n     * @param { Object } selection\n     * @param { Node } selection.anchorNode\n     * @param { number } selection.anchorOffset\n     * @param { Node } [selection.focusNode=selection.anchorNode]\n     * @param { number } [selection.focusOffset=selection.anchorOffset]\n     * @param { Object } [options]\n     * @param { boolean } [options.normalize=true] Normalize deep the selection\n     * @return { EditorSelection | null }\n     */\n    setSelection(\n        { anchorNode, anchorOffset, focusNode = anchorNode, focusOffset = anchorOffset },\n        { normalize = true } = {}\n    ) {\n        if (!this.validateSelection({ anchorNode, anchorOffset, focusNode, focusOffset })) {\n            return null;\n        }\n        const restore = this.preserveTextareaSelections();\n        const isCollapsed = anchorNode === focusNode && anchorOffset === focusOffset;\n        [focusNode, focusOffset] = normalizeSelfClosingElement(focusNode, focusOffset, \"right\");\n        [anchorNode, anchorOffset] = isCollapsed\n            ? [focusNode, focusOffset]\n            : normalizeSelfClosingElement(anchorNode, anchorOffset, \"left\");\n        if (normalize) {\n            // normalize selection\n            [anchorNode, anchorOffset] = normalizeDeepCursorPosition(anchorNode, anchorOffset);\n            [focusNode, focusOffset] = isCollapsed\n                ? [anchorNode, anchorOffset]\n                : normalizeDeepCursorPosition(focusNode, focusOffset);\n        }\n\n        [anchorNode, anchorOffset] = normalizeFakeBR(anchorNode, anchorOffset);\n        [focusNode, focusOffset] = normalizeFakeBR(focusNode, focusOffset);\n        const selection = this.document.getSelection();\n        const documentSelectionIsInEditable = selection && this.isSelectionInEditable(selection);\n        if (selection) {\n            if (documentSelectionIsInEditable || selection.anchorNode === null) {\n                selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);\n                this.activeSelection = this.makeActiveSelection(selection, true);\n            } else {\n                let range = new Range();\n                range.setStart(anchorNode, anchorOffset);\n                range.setEnd(focusNode, focusOffset);\n                if (anchorNode !== focusNode || anchorOffset !== focusOffset) {\n                    // Check if the direction is correct\n                    if (range.collapsed) {\n                        range = new Range();\n                        range.setEnd(anchorNode, anchorOffset);\n                        range.setStart(focusNode, focusOffset);\n                    }\n                }\n\n                this.activeSelection = this.makeActiveSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                    getRangeAt: () => range,\n                    rangeCount: 1,\n                });\n            }\n        }\n        restore();\n\n        return this.activeSelection;\n    }\n\n    /**\n     * Take the selections in all `<textarea>` elements in the editable and\n     * return a function that restores them.\n     *\n     * @returns {() => void}\n     */\n    preserveTextareaSelections() {\n        const focusedTextarea =\n            this.document.activeElement?.nodeName === \"TEXTAREA\" && this.document.activeElement;\n        const selections = [...this.editable.querySelectorAll(\"textarea\")].map((textarea) => ({\n            textarea,\n            start: textarea.selectionStart,\n            end: textarea.selectionEnd,\n            direction: textarea.selectionDirection,\n        }));\n        return () => {\n            if (focusedTextarea) {\n                // If a textarea is targeted, focus it so its selection is active.\n                focusedTextarea.focus();\n            }\n            for (const { textarea, start, end, direction } of selections) {\n                textarea.setSelectionRange(start, end, direction);\n            }\n        };\n    }\n\n    /**\n     * Set the cursor at the start of the given node.\n     * @param { Node } node\n     */\n    setCursorStart(node) {\n        return this.setSelection({ anchorNode: node, anchorOffset: 0 });\n    }\n\n    /**\n     * Set the cursor at the end of the given node.\n     * @param { Node } node\n     */\n    setCursorEnd(node) {\n        return this.setSelection({ anchorNode: node, anchorOffset: nodeSize(node) });\n    }\n\n    /**\n     * Stores the current selection and returns an object with methods to:\n     * - update the cursors (anchor and focus) node and offset after DOM\n     * manipulations that migh affect them. Such methods are chainable.\n     * - restore the updated selection.\n     * @returns {Cursors}\n     */\n    preserveSelection() {\n        const hadSelection =\n            this.document.getSelection() && this.document.getSelection().anchorNode !== null;\n        const selectionData = this.getSelectionData();\n        const selection = selectionData.editableSelection;\n        const anchor = { node: selection.anchorNode, offset: selection.anchorOffset };\n        const focus = { node: selection.focusNode, offset: selection.focusOffset };\n\n        return {\n            restore: () => {\n                if (!hadSelection) {\n                    return;\n                }\n                this.setSelection(\n                    {\n                        anchorNode: anchor.node,\n                        anchorOffset: anchor.offset,\n                        focusNode: focus.node,\n                        focusOffset: focus.offset,\n                    },\n                    { normalize: false }\n                );\n            },\n            update(callback) {\n                callback(anchor);\n                callback(focus);\n                return this;\n            },\n            remapNode(node, newNode) {\n                return this.update((cursor) => {\n                    if (cursor.node === node) {\n                        cursor.node = newNode;\n                    }\n                });\n            },\n            setOffset(node, newOffset) {\n                return this.update((cursor) => {\n                    if (cursor.node === node) {\n                        cursor.offset = newOffset;\n                    }\n                });\n            },\n            shiftOffset(node, shiftOffset) {\n                return this.update((cursor) => {\n                    if (cursor.node === node) {\n                        cursor.offset += shiftOffset;\n                    }\n                });\n            },\n        };\n    }\n\n    areNodeContentsFullySelected(node) {\n        const selection = this.getEditableSelection();\n        const range = new Range();\n        range.setStart(selection.startContainer, selection.startOffset);\n        range.setEnd(selection.endContainer, selection.endOffset);\n\n        const firstLeafNode = firstLeaf(node);\n        const lastLeafNode = lastLeaf(node);\n        return (\n            // Custom rules\n            this.getResource(\"fully_selected_node_predicates\").some((cb) =>\n                cb(node, selection, range)\n            ) ||\n            // Default rule\n            (range.isPointInRange(firstLeafNode, 0) &&\n                range.isPointInRange(lastLeafNode, nodeSize(lastLeafNode)))\n        );\n    }\n\n    /**\n     * Returns the nodes targeted by the current selection, from top to bottom\n     * and left to right.\n     * This includes nodes intersected by the selection, as well as the deepest\n     * anchor and offset nodes that are at least partly contained in the\n     * selection.\n     * An element is considered intersected by the selection when reading the\n     * normalized selection's HTML contents would involve reading the opening or\n     * closing tags of the element.\n     * A collapsed selection returns the node in which it is collapsed.\n     *\n     * @example\n     * <p>a[]b</p> -> [\"ab\"]\n     * @example\n     * <p>a[b</p><h1>c]d</h1> -> [P, \"ab\", H1, \"cd\"]\n     * @example\n     * <p>a[b</p><h1>]cd</h1> -> [P, \"ab\", H1]\n     * @example\n     * <div><p>a[b</p><h1>cd</h1></div><h2>e]f</h2> -> [DIV, P, \"ab\", H1, \"cd\", H2, \"ef\"]\n     *\n     * @returns {Node[]}\n     */\n    getTargetedNodes() {\n        const selectionData = this.getSelectionData();\n        const selection = selectionData.deepEditableSelection;\n        const { commonAncestorContainer: root } = selectionData.editableSelection;\n\n        let targetedNodes = [];\n        if (selection.isCollapsed && selection.anchorNode.nodeType !== Node.TEXT_NODE) {\n            targetedNodes = [root];\n        }\n        targetedNodes.push(...descendants(root));\n        if (!targetedNodes.length) {\n            targetedNodes = [root];\n        }\n\n        targetedNodes = targetedNodes.filter(\n            (node) =>\n                selectionData.editableSelection.intersectsNode(node) ||\n                (node.nodeType === Node.TEXT_NODE &&\n                    (node === selection.anchorNode || node === selection.focusNode))\n        );\n\n        const modifiers = [\n            // Remove the editable from the list\n            (nodes) => (nodes[0] === this.editable ? nodes.slice(1) : nodes),\n            // Filter out text nodes that have no content selected\n            (nodes) => {\n                if (selection.isCollapsed) {\n                    return nodes;\n                } else {\n                    const edgeTextNodes = getUnselectedEdgeTextNodes(selection);\n                    return nodes.filter((node) => !edgeTextNodes.has(node));\n                }\n            },\n            // Custom modifiers\n            ...this.getResource(\"targeted_nodes_processors\"),\n        ];\n        for (const modifier of modifiers) {\n            targetedNodes = modifier(targetedNodes);\n        }\n        return targetedNodes;\n    }\n\n    /**\n     * Returns a Set of targeted blocks within the given range.\n     *\n     * @returns {Set<HTMLElement>}\n     */\n    getTargetedBlocks() {\n        return new Set(this.getTargetedNodes().map(closestBlock).filter(Boolean));\n    }\n\n    // @todo @phoenix we should find a real use case and test it\n    // /**\n    //  * Set a deep selection that split the text and collapse it if only one ZWS is\n    //  * selected.\n    //  *\n    //  * @returns {boolean} true if the selection has only one ZWS.\n    //  */\n    // collapseIfZWS() {\n    //     const selection = this.getSelectionData().deepEditableSelection;\n    //     if (\n    //         selection.startContainer === selection.endContainer &&\n    //         selection.startContainer.nodeType === Node.TEXT_NODE &&\n    //         selection.startContainer.textContent === \"\\u200B\"\n    //     ) {\n    //         // We Collapse the selection and bypass deleteRange\n    //         // if the range content is only one ZWS.\n    //         this.setCursorStart(selection.startContainer);\n    //         return true;\n    //     }\n    //     return false;\n    // }\n\n    /**\n     * @param {SelectionData} selectionData\n     * @returns {boolean} Whether the selection was fixed\n     */\n    fixSelectionOnEditableRoot(selectionData) {\n        const { editableSelection, documentSelectionIsInEditable } = selectionData;\n        if (this.config.allowInlineAtRoot || !documentSelectionIsInEditable) {\n            return false;\n        }\n        const isSelectionOnEditableRoot = (s) => s.isCollapsed && s.anchorNode === this.editable;\n        if (!isSelectionOnEditableRoot(editableSelection)) {\n            return false;\n        }\n        if (this.delegateTo(\"fix_selection_on_editable_root_overrides\", editableSelection)) {\n            return true;\n        }\n        // Revert the selection to the previous one\n        if (isSelectionOnEditableRoot(this.previousActiveSelection)) {\n            // Last stored selection is also at the editable root\n            return false;\n        }\n        const selection = this.document.getSelection();\n        if (!selection) {\n            return false;\n        }\n        const { anchorNode, anchorOffset, focusNode, focusOffset } = this.previousActiveSelection;\n        selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);\n        return true;\n    }\n\n    /**\n     * This function adjusts a given selection to the current nodeSize of its\n     * anchorNode and focusNode, only if they are both present in the given\n     * editable. Apply and return: a valid given selection, a modified\n     * selection if some offset needed to be adjusted. Do nothing if the given\n     * selection anchor or focus nodes are not in this.editable.\n     *\n     * @param { Object } selection\n     * @param { Node } selection.anchorNode\n     * @param { number } selection.anchorOffset\n     * @param { Node } selection.focusNode\n     * @param { number } selection.focusOffset\n     * @returns { EditorSelection|null } selection, rectified selection or null\n     */\n    rectifySelection(selection) {\n        if (!this.isSelectionInEditable(selection)) {\n            return null;\n        }\n        const anchorNode = selection.anchorNode;\n        let anchorOffset = selection.anchorOffset;\n        const focusNode = selection.focusNode;\n        let focusOffset = selection.focusOffset;\n        const anchorSize = nodeSize(anchorNode);\n        const focusSize = nodeSize(focusNode);\n        if (anchorSize < anchorOffset) {\n            anchorOffset = anchorSize;\n        }\n        if (focusSize < focusOffset) {\n            focusOffset = focusSize;\n        }\n        const anchorTarget = childNodes(anchorNode).at(anchorOffset);\n        const focusTarget = childNodes(focusNode).at(focusOffset);\n        const protectionCheck = (node) =>\n            isProtecting(node) || (isProtected(node) && !isUnprotecting(node));\n        if (\n            focusTarget !== anchorTarget &&\n            focusTarget.previousSibling === anchorTarget &&\n            protectionCheck(anchorTarget)\n        ) {\n            return;\n        }\n        if (protectionCheck(anchorNode) || protectionCheck(focusNode)) {\n            // TODO @phoenix, TODO ABD: better handle setSelection on protected\n            // elements\n            return;\n        }\n        return this.setSelection({\n            anchorNode,\n            anchorOffset,\n            focusNode,\n            focusOffset,\n        });\n    }\n\n    /**\n     * @param {\"move\"|\"extend\"} alter\n     * @param {\"backward\"|\"forward\"} direction\n     * @param {\"character\"|\"word\"|\"line\"} granularity\n     * @returns {EditorSelection}\n     */\n    modifySelection(alter, direction, granularity) {\n        const selectionData = this.getSelectionData();\n        if (!selectionData.documentSelectionIsInEditable) {\n            return selectionData.editableSelection;\n        }\n        const selection = this.document.getSelection();\n        if (!selection) {\n            return selectionData.editableSelection;\n        }\n        selection.modify(alter, direction, granularity);\n        if (!this.isSelectionInEditable(selection)) {\n            // If selection was moved to outside the editable, restore it.\n            return this.setSelection(selectionData.editableSelection);\n        }\n        this.activeSelection = this.makeActiveSelection(selection);\n        return this.activeSelection;\n    }\n\n    /**\n     * Changes the selection before the browser's default behavior moves the\n     * cursor, in order to skip undesired characters (typically invisible\n     * characters).\n     */\n    onKeyDownArrows(ev) {\n        const selection = this.document.getSelection();\n        if (!selection || !this.isSelectionInEditable(selection)) {\n            return;\n        }\n\n        // Whether moving a collapsed cursor or extending a selection.\n        const mode = ev.shiftKey ? \"extend\" : \"move\";\n\n        if ([\"ArrowLeft\", \"ArrowRight\"].includes(ev.key)) {\n            // Direction of the movement (take rtl writing into account)\n            const screenDirection = ev.key === \"ArrowLeft\" ? \"left\" : \"right\";\n            const isRtl = closestElement(selection.focusNode, \"[dir]\")?.dir === \"rtl\";\n            const domDirection = (screenDirection === \"left\") ^ isRtl ? \"previous\" : \"next\";\n\n            // Whether the character next to the cursor should be skipped.\n            const shouldSkipCallbacks = this.getResource(\n                \"intangible_char_for_keyboard_navigation_predicates\"\n            );\n            let adjacentCharacter = getAdjacentCharacter(selection, domDirection, this.editable);\n            let shouldSkip = shouldSkipCallbacks.some((cb) => cb(ev, adjacentCharacter));\n\n            while (shouldSkip) {\n                const { focusNode: nodeBefore, focusOffset: offsetBefore } = selection;\n\n                selection.modify(mode, screenDirection, \"character\");\n\n                const hasSelectionChanged =\n                    nodeBefore !== selection.focusNode || offsetBefore !== selection.focusOffset;\n                const lastSkippedChar = adjacentCharacter;\n                adjacentCharacter = getAdjacentCharacter(selection, domDirection, this.editable);\n\n                shouldSkip =\n                    hasSelectionChanged &&\n                    shouldSkipCallbacks.some((cb) => cb(ev, adjacentCharacter, lastSkippedChar));\n            }\n        }\n\n        const { focusNode, focusOffset } = selection;\n        if (mode === \"extend\") {\n            // Since selection can't traverse contenteditable=\"false\" elements,\n            // we adjust the selection to the sibling of non editable element.\n            const selectingBackward = [\"ArrowLeft\", \"ArrowUp\"].includes(ev.key);\n            const currentBlock = closestBlock(focusNode);\n            const isAtBoundary = selectingBackward\n                ? firstLeaf(currentBlock) === focusNode && focusOffset === 0\n                : lastLeaf(currentBlock) === focusNode && focusOffset === nodeSize(focusNode);\n            const adjacentBlock = selectingBackward\n                ? currentBlock.previousElementSibling\n                : currentBlock.nextElementSibling;\n            const targetBlock = selectingBackward\n                ? adjacentBlock?.previousElementSibling\n                : adjacentBlock?.nextElementSibling;\n            if (!adjacentBlock?.isContentEditable && targetBlock && isAtBoundary) {\n                const leafNode = selectingBackward ? lastLeaf(targetBlock) : firstLeaf(targetBlock);\n                const offset = selectingBackward ? nodeSize(leafNode) : 0;\n                selection.extend(leafNode, offset);\n                ev.preventDefault();\n            }\n        }\n    }\n\n    isSelectionInEditable({ anchorNode, focusNode } = {}) {\n        return (\n            !!anchorNode &&\n            !!focusNode &&\n            this.editable.contains(anchorNode) &&\n            (focusNode === anchorNode || this.editable.contains(focusNode))\n        );\n    }\n\n    isNodeEditable(node) {\n        const results = this.getResource(\"is_node_editable_predicates\")\n            .map((p) => p(node))\n            .filter((r) => r !== undefined);\n        if (!results.length) {\n            return node.parentElement?.isContentEditable;\n        }\n        return results.every((r) => r);\n    }\n\n    focusEditable() {\n        const { editableSelection, currentSelectionIsInEditable } = this.getSelectionData();\n        if (this.editable.contains(this.document.activeElement) && currentSelectionIsInEditable) {\n            // Editor has focus \u2014 nothing to do.\n            return;\n        }\n\n        const closestNonEditable = (node) => closestElement(node, (el) => !el.isContentEditable);\n        // If selection includes a non-editable element, focusing editor will move cursor to different position.\n        if (\n            !closestNonEditable(editableSelection.anchorNode) &&\n            !closestNonEditable(editableSelection.focusNode)\n        ) {\n            // Manualy focusing the editable is necessary to avoid some non-deterministic error in the HOOT unit tests.\n            this.editable.focus({ preventScroll: true });\n        }\n\n        if (!currentSelectionIsInEditable) {\n            // Selection is outside the editor \u2014 restore it.\n            const { anchorNode, anchorOffset, focusNode, focusOffset } = editableSelection;\n            const selection = this.document.getSelection();\n            if (selection) {\n                selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);\n            }\n        }\n    }\n\n    /**\n     * @returns {EditorSelection}\n     */\n    selectAroundNonEditable() {\n        // Get up-to-date selection\n        const { editableSelection } = this.getSelectionData();\n        // Avoid setting the selection if it's not inside an uneditable element\n        const isInUneditable = (node) => !!closestElement(node, (elem) => !elem.isContentEditable);\n        let { startContainer: start, endContainer: end } = editableSelection;\n        if (!(isInUneditable(start) || (end !== start && isInUneditable(end)))) {\n            return editableSelection;\n        }\n        // Normalize both sides\n        let { startOffset, endOffset, direction } = editableSelection;\n        [start, startOffset] = normalizeNotEditableNode(start, startOffset, \"left\");\n        [end, endOffset] = normalizeNotEditableNode(end, endOffset, \"right\");\n        // Set the new selection\n        const [anchorNode, anchorOffset, focusNode, focusOffset] = direction\n            ? [start, startOffset, end, endOffset]\n            : [end, endOffset, start, startOffset];\n        return this.setSelection({ anchorNode, anchorOffset, focusNode, focusOffset });\n    }\n}\n", "import { Plugin, isValidTargetForDomListener } from \"../plugin\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { fillEmpty } from \"@html_editor/utils/dom\";\nimport { leftLeafOnlyNotBlockPath } from \"@html_editor/utils/dom_state\";\n\n/**\n * @typedef {Object} Shortcut\n * @property {string} hotkey\n * @property {string} commandId\n * @property {Object} [commandParams]\n * @property {boolean} [global]\n *\n * @typedef {Shortcut[]} shortcuts\n *\n * Example:\n *\n *     resources = {\n *         // See UserCommand\n *         user_commands: [\n *             { id: \"myCommands\", run: myCommandFunction },\n *         ],\n *         // See Shortcut\n *         shortcuts: [\n *             { hotkey: \"control+shift+q\", commandId: \"myCommands\" },\n *         ],\n *     }\n */\n\n/**\n * @typedef {{\n *     pattern: RegExp;\n *     commandId: string;\n *     commandParams?: object;\n * }[]} shorthands\n */\n\nexport class ShortCutPlugin extends Plugin {\n    static id = \"shortcut\";\n    static dependencies = [\"userCommand\", \"selection\"];\n\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        input_handlers: this.onInput.bind(this),\n    };\n\n    setup() {\n        const hotkeyService = this.services.hotkey;\n        if (!hotkeyService) {\n            throw new Error(\"ShorcutPlugin needs hotkey service to properly work\");\n        }\n        if (document !== this.document) {\n            hotkeyService.registerIframe({ contentWindow: this.window });\n        }\n        for (const shortcut of this.getResource(\"shortcuts\")) {\n            const command = this.dependencies.userCommand.getCommand(shortcut.commandId);\n            this.addShortcut(\n                shortcut.hotkey,\n                () => {\n                    command.run(shortcut.commandParams);\n                },\n                {\n                    isAvailable: command.isAvailable,\n                    global: !!shortcut.global,\n                }\n            );\n        }\n    }\n\n    addShortcut(hotkey, action, { isAvailable, global }) {\n        this._cleanups.push(\n            this.services.hotkey.add(hotkey, action, {\n                area: () => this.editable,\n                bypassEditableProtection: true,\n                allowRepeat: true,\n                isAvailable: (target) =>\n                    (!isAvailable ||\n                        isAvailable(this.dependencies.selection.getEditableSelection())) &&\n                    (global || isValidTargetForDomListener(target)),\n            })\n        );\n    }\n\n    onInput(ev) {\n        if (ev.data !== \" \") {\n            return;\n        }\n        const selection = this.dependencies.selection.getEditableSelection();\n        const blockEl = closestBlock(selection.anchorNode);\n        const leftDOMPath = leftLeafOnlyNotBlockPath(selection.anchorNode);\n        let spaceOffset = selection.anchorOffset;\n        let leftLeaf = leftDOMPath.next().value;\n        while (leftLeaf) {\n            // Calculate spaceOffset by adding lengths of previous text nodes\n            // to correctly find offset position for selection within inline\n            // elements. e.g. <p>ab<strong>cd []e</strong></p>\n            spaceOffset += leftLeaf.length;\n            leftLeaf = leftDOMPath.next().value;\n        }\n        const precedingText = blockEl.textContent.substring(0, spaceOffset - 1);\n        const matchedShortcut = this.getResource(\"shorthands\").find(({ pattern }) =>\n            pattern.test(precedingText)\n        );\n        if (matchedShortcut) {\n            const command = this.dependencies.userCommand.getCommand(matchedShortcut.commandId);\n            if (command) {\n                this.dependencies.selection.setSelection({\n                    anchorNode: blockEl.firstChild,\n                    anchorOffset: 0,\n                    focusNode: selection.focusNode,\n                    focusOffset: selection.focusOffset,\n                });\n                this.dependencies.selection.extractContent(\n                    this.dependencies.selection.getEditableSelection()\n                );\n                fillEmpty(blockEl);\n                command.run(matchedShortcut.commandParams);\n            }\n        }\n    }\n}\n", "import { callbacksForCursorUpdate } from \"@html_editor/utils/selection\";\nimport { Plugin } from \"../plugin\";\nimport { isBlock } from \"../utils/blocks\";\nimport { fillEmpty, splitTextNode } from \"../utils/dom\";\nimport {\n    isContentEditable,\n    isContentEditableAncestor,\n    isElement,\n    isTextNode,\n    isVisible,\n} from \"../utils/dom_info\";\nimport { prepareUpdate } from \"../utils/dom_state\";\nimport {\n    childNodes,\n    closestElement,\n    descendants,\n    firstLeaf,\n    lastLeaf,\n} from \"../utils/dom_traversal\";\nimport { DIRECTIONS, childNodeIndex, nodeSize } from \"../utils/position\";\nimport { isProtected, isProtecting } from \"@html_editor/utils/dom_info\";\n\n/**\n * @typedef { Object } SplitShared\n * @property { SplitPlugin['isUnsplittable'] } isUnsplittable\n * @property { SplitPlugin['splitAroundUntil'] } splitAroundUntil\n * @property { SplitPlugin['splitBlock'] } splitBlock\n * @property { SplitPlugin['splitBlockNode'] } splitBlockNode\n * @property { SplitPlugin['splitElement'] } splitElement\n * @property { SplitPlugin['splitElementBlock'] } splitElementBlock\n * @property { SplitPlugin['splitSelection'] } splitSelection\n */\n\n/**\n * @typedef {(({element: HTMLElement, secondPart: HTMLElement}) => void)[]} after_split_element_handlers\n * @typedef {(() => void)[]} before_split_block_handlers\n *\n * @typedef {((params: { targetNode: Node, targetOffset: number, blockToSplit: HTMLElement | null }) => void | true)[]} split_element_block_overrides\n *\n * @typedef {((node: Node) => boolean)[]} unsplittable_node_predicates\n */\n\nexport class SplitPlugin extends Plugin {\n    static dependencies = [\"baseContainer\", \"selection\", \"history\", \"input\", \"delete\", \"lineBreak\"];\n    static id = \"split\";\n    static shared = [\n        \"splitBlock\",\n        \"splitBlockNode\",\n        \"splitElementBlock\",\n        \"splitElement\",\n        \"splitAroundUntil\",\n        \"splitSelection\",\n        \"isUnsplittable\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        beforeinput_handlers: this.onBeforeInput.bind(this),\n\n        unsplittable_node_predicates: [\n            // An unremovable element is also unmergeable (as merging two\n            // elements results in removing one of them).\n            // An unmergeable element is unsplittable and vice-versa (as\n            // split and merge are reverse operations from one another).\n            // Therefore, unremovable nodes are also unsplittable.\n            (node) =>\n                this.getResource(\"unremovable_node_predicates\").some((predicate) =>\n                    predicate(node)\n                ),\n            // \"Unbreakable\" is a legacy term that means unsplittable and\n            // unmergeable.\n            (node) => node.classList?.contains(\"oe_unbreakable\"),\n            (node) => {\n                const isExplicitlyNotContentEditable = (node) =>\n                    // In the `contenteditable` attribute consideration,\n                    // disconnected nodes can be unsplittable only if they are\n                    // explicitly set under a contenteditable=\"false\" element.\n                    !isContentEditable(node) &&\n                    (node.isConnected || closestElement(node, \"[contenteditable]\"));\n                return (\n                    isExplicitlyNotContentEditable(node) ||\n                    // If node sets contenteditable='true' and is inside a non-editable\n                    // context, it has to be unsplittable since splitting it would modify\n                    // the non-editable parent content.\n                    (node.parentElement &&\n                        isContentEditableAncestor(node) &&\n                        isExplicitlyNotContentEditable(node.parentElement))\n                );\n            },\n            (node) => node.nodeName === \"SECTION\",\n        ],\n        selection_blocker_predicates: (blocker) => {\n            if (this.isUnsplittable(blocker)) {\n                return true;\n            }\n        },\n    };\n\n    // --------------------------------------------------------------------------\n    // commands\n    // --------------------------------------------------------------------------\n    splitBlock() {\n        this.dispatchTo(\"before_split_block_handlers\");\n        let selection = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        if (!selection.isCollapsed) {\n            // @todo @phoenix collapseIfZWS is not tested\n            // this.shared.collapseIfZWS();\n            this.dependencies.delete.deleteSelection();\n            selection = this.dependencies.selection.getEditableSelection();\n        }\n\n        return this.splitBlockNode({\n            targetNode: selection.anchorNode,\n            targetOffset: selection.anchorOffset,\n        });\n    }\n\n    /**\n     * @param {Object} param0\n     * @param {Node} param0.targetNode\n     * @param {number} param0.targetOffset\n     * @returns {[HTMLElement|undefined, HTMLElement|undefined]}\n     */\n    splitBlockNode({ targetNode, targetOffset }) {\n        if (targetNode.nodeType === Node.TEXT_NODE) {\n            targetOffset = splitTextNode(targetNode, targetOffset);\n            targetNode = targetNode.parentElement;\n        }\n        const blockToSplit = closestElement(targetNode, isBlock);\n        const params = { targetNode, targetOffset, blockToSplit };\n\n        if (this.delegateTo(\"split_element_block_overrides\", params)) {\n            return [undefined, undefined];\n        }\n\n        return this.splitElementBlock(params);\n    }\n    /**\n     * @param {Object} param0\n     * @param {HTMLElement} param0.targetNode\n     * @param {number} param0.targetOffset\n     * @param {HTMLElement} param0.blockToSplit\n     * @returns {[HTMLElement|undefined, HTMLElement|undefined]}\n     */\n    splitElementBlock({ targetNode, targetOffset, blockToSplit }) {\n        // If the block is unsplittable, insert a line break instead.\n        if (this.isUnsplittable(blockToSplit)) {\n            // @todo: t-if, t-else etc are not blocks, but they are\n            // unsplittable.  The check must be done from the targetNode up to\n            // the block for unsplittables. There are apparently no tests for\n            // this.\n            this.dependencies.lineBreak.insertLineBreakElement({ targetNode, targetOffset });\n            return [undefined, undefined];\n        }\n        const restore = prepareUpdate(targetNode, targetOffset);\n\n        const [beforeElement, afterElement] = this.splitElementUntil(\n            targetNode,\n            targetOffset,\n            blockToSplit.parentElement\n        );\n        restore();\n        const fillEmptyElement = (node) => {\n            if (isProtecting(node) || isProtected(node)) {\n                // TODO ABD: add test\n                return;\n            } else if (node.nodeType === Node.TEXT_NODE && !isVisible(node)) {\n                const parent = node.parentElement;\n                node.remove();\n                fillEmptyElement(parent);\n            } else if (node.nodeType === Node.ELEMENT_NODE) {\n                if (node.hasAttribute(\"data-oe-zws-empty-inline\")) {\n                    delete node.dataset.oeZwsEmptyInline;\n                }\n                fillEmpty(node);\n            }\n        };\n        fillEmptyElement(lastLeaf(beforeElement));\n        fillEmptyElement(firstLeaf(afterElement));\n\n        this.dependencies.selection.setCursorStart(afterElement);\n\n        return [beforeElement, afterElement];\n    }\n\n    /**\n     * @param {Node} node\n     * @returns {boolean}\n     */\n    isUnsplittable(node) {\n        return this.getResource(\"unsplittable_node_predicates\").some((p) => p(node));\n    }\n\n    /**\n     * Split the given element at the given offset. The element will be removed in\n     * the process so caution is advised in dealing with its reference. Returns a\n     * tuple containing the new elements on both sides of the split.\n     *\n     * @param {HTMLElement} element\n     * @param {number} offset\n     * @returns {[HTMLElement, HTMLElement]}\n     */\n    splitElement(element, offset) {\n        /** @type {HTMLElement} **/\n        const firstPart = element.cloneNode();\n        /** @type {HTMLElement} **/\n        const secondPart = element.cloneNode();\n        element.before(firstPart);\n        element.after(secondPart);\n        const children = childNodes(element);\n        firstPart.append(...children.slice(0, offset));\n        secondPart.append(...children.slice(offset));\n        element.remove();\n        this.dispatchTo(\"after_split_element_handlers\", { firstPart, secondPart });\n        return [firstPart, secondPart];\n    }\n\n    /**\n     * Split the given element at the given offset, until the given limit ancestor.\n     * The element will be removed in the process so caution is advised in dealing\n     * with its reference. Returns a tuple containing the new elements on both sides\n     * of the split.\n     *\n     * @param {HTMLElement} element\n     * @param {number} offset\n     * @param {HTMLElement} limitAncestor\n     * @returns {[HTMLElement, HTMLElement]}\n     */\n    splitElementUntil(element, offset, limitAncestor) {\n        if (element === limitAncestor) {\n            return [element, element];\n        }\n        let [before, after] = this.splitElement(element, offset);\n        if (after.parentElement !== limitAncestor) {\n            const afterIndex = childNodeIndex(after);\n            [before, after] = this.splitElementUntil(\n                after.parentElement,\n                afterIndex,\n                limitAncestor\n            );\n        }\n        return [before, after];\n    }\n\n    /**\n     * Split around the given elements, until a given ancestor (included). Elements\n     * will be removed in the process so caution is advised in dealing with their\n     * references. Returns the new split root element that is a clone of\n     * limitAncestor or the original limitAncestor if no split occured.\n     *\n     * @param {Node[] | Node} elements\n     * @param {HTMLElement} limitAncestor\n     * @returns { Node }\n     */\n    splitAroundUntil(elements, limitAncestor, cursors = null) {\n        elements = Array.isArray(elements) ? elements : [elements];\n        const firstNode = elements[0];\n        const lastNode = elements[elements.length - 1];\n        if ([firstNode, lastNode].includes(limitAncestor)) {\n            return limitAncestor;\n        }\n        let before = firstNode.previousSibling;\n        let after = lastNode.nextSibling;\n        let beforeSplit, afterSplit;\n        if (\n            !before &&\n            !after &&\n            firstNode.parentElement !== limitAncestor &&\n            lastNode.parentElement !== limitAncestor\n        ) {\n            return this.splitAroundUntil(\n                [firstNode.parentElement, lastNode.parentElement],\n                limitAncestor,\n                cursors\n            );\n        } else if (!after && lastNode.parentElement !== limitAncestor) {\n            return this.splitAroundUntil(\n                [firstNode, lastNode.parentElement],\n                limitAncestor,\n                cursors\n            );\n        } else if (!before && firstNode.parentElement !== limitAncestor) {\n            return this.splitAroundUntil(\n                [firstNode.parentElement, lastNode],\n                limitAncestor,\n                cursors\n            );\n        }\n        // Split up ancestors up to font\n        while (after && after.parentElement !== limitAncestor) {\n            afterSplit = this.splitElement(after.parentElement, childNodeIndex(after))[0];\n            after = afterSplit.nextSibling;\n        }\n        if (after) {\n            afterSplit = this.splitElement(limitAncestor, childNodeIndex(after))[0];\n            limitAncestor = afterSplit;\n        }\n        while (before && before.parentElement !== limitAncestor) {\n            beforeSplit = this.splitElement(before.parentElement, childNodeIndex(before) + 1)[1];\n            before = beforeSplit.previousSibling;\n        }\n        if (before) {\n            beforeSplit = this.splitElement(limitAncestor, childNodeIndex(before) + 1)[1];\n        }\n        const result = beforeSplit || afterSplit || limitAncestor;\n        this.fixSplitAroundUntilEmptyNodes(result.parentElement, cursors);\n        return result;\n    }\n\n    /**\n     * Fix for stable to remove empty nodes created by `splitAroundUntil`\n     * and properly manage the cursor.\n     * @param {Node} node\n     * @param {HTMLElement} limitAncestor\n     * @returns { Node }\n     */\n    fixSplitAroundUntilEmptyNodes(node, cursors) {\n        node &&\n            descendants(node)\n                .filter(\n                    (node) =>\n                        isElement(node) &&\n                        node.childNodes.length &&\n                        [...node.childNodes].every((n) => isTextNode(n)) &&\n                        !node.textContent.replaceAll(\"\\ufeff\", \"\")\n                )\n                .forEach((node) => {\n                    cursors?.update(callbacksForCursorUpdate.remove(node));\n                    node.remove();\n                });\n    }\n\n    splitSelection() {\n        let { startContainer, startOffset, endContainer, endOffset, direction } =\n            this.dependencies.selection.getEditableSelection();\n        const isInSingleContainer = startContainer === endContainer;\n        if (isTextNode(endContainer) && endOffset > 0 && endOffset < nodeSize(endContainer)) {\n            const endParent = endContainer.parentNode;\n            const splitOffset = splitTextNode(endContainer, endOffset);\n            endContainer = endParent.childNodes[splitOffset - 1] || endParent.firstChild;\n            if (isInSingleContainer) {\n                startContainer = endContainer;\n            }\n            endOffset = endContainer.textContent.length;\n        }\n        if (\n            isTextNode(startContainer) &&\n            startOffset > 0 &&\n            startOffset < nodeSize(startContainer)\n        ) {\n            splitTextNode(startContainer, startOffset);\n            startOffset = 0;\n            if (isInSingleContainer) {\n                endOffset = startContainer.textContent.length;\n            }\n        }\n\n        const selection =\n            direction === DIRECTIONS.RIGHT\n                ? {\n                      anchorNode: startContainer,\n                      anchorOffset: startOffset,\n                      focusNode: endContainer,\n                      focusOffset: endOffset,\n                  }\n                : {\n                      anchorNode: endContainer,\n                      anchorOffset: endOffset,\n                      focusNode: startContainer,\n                      focusOffset: startOffset,\n                  };\n        return this.dependencies.selection.setSelection(selection, { normalize: false });\n    }\n\n    onBeforeInput(e) {\n        if (e.inputType === \"insertParagraph\") {\n            e.preventDefault();\n            this.splitBlock();\n            this.dependencies.history.addStep();\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { backgroundImageCssToParts, backgroundImagePartsToCss } from \"@html_editor/utils/image\";\n\n/**\n * @typedef { Object } StyleShared\n * @property { StylePlugin['setBackgroundImageUrl'] } setBackgroundImageUrl\n */\n\nexport class StylePlugin extends Plugin {\n    static id = \"style\";\n    static shared = [\"setBackgroundImageUrl\"];\n\n    setBackgroundImageUrl(el, value) {\n        const parts = backgroundImageCssToParts(el.style[\"background-image\"]);\n        if (value) {\n            parts.url = `url('${value}')`;\n        } else {\n            delete parts.url;\n        }\n        el.style[\"background-image\"] = backgroundImagePartsToCss(parts);\n    }\n}\n", "import { Plugin } from \"../plugin\";\n\n/**\n * @typedef { import(\"./selection_plugin\").EditorSelection } EditorSelection\n */\n\n/**\n * @typedef { Object } UserCommand\n * @property { string } id\n * @property { Function } run\n * @property { String } [title]\n * @property { String } [description]\n * @property { string } [icon]\n * @property { (selection: EditorSelection) => boolean  } [isAvailable]\n */\n\n/**\n * @typedef { Object } UserCommandShared\n * @property { UserCommandPlugin['getCommand'] } getCommand\n */\n\n/**\n * @typedef {UserCommand[]} user_commands\n */\n\nexport class UserCommandPlugin extends Plugin {\n    static id = \"userCommand\";\n    static shared = [\"getCommand\"];\n\n    setup() {\n        this.commands = {};\n        for (const command of this.getResource(\"user_commands\")) {\n            if (command.id in this.commands) {\n                throw new Error(`Duplicate user command id: ${command.id}`);\n            }\n            this.commands[command.id] = command;\n        }\n        Object.freeze(this.commands);\n    }\n\n    /**\n     * @param {string} commandId\n     * @returns {UserCommand}\n     * @throws {Error} if the command ID is unknown.\n     */\n    getCommand(commandId) {\n        const command = this.commands[commandId];\n        if (!command) {\n            throw new Error(`Unknown user command id: ${commandId}`);\n        }\n        return command;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { isVisibleTextNode } from \"@html_editor/utils/dom_info\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { AlignSelector } from \"./align_selector\";\nimport { reactive } from \"@odoo/owl\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { weakMemoize } from \"@html_editor/utils/functions\";\n\nconst alignmentItems = [\n    { mode: \"left\" },\n    { mode: \"center\" },\n    { mode: \"right\" },\n    { mode: \"justify\" },\n];\n\nexport class AlignPlugin extends Plugin {\n    static id = \"align\";\n    static dependencies = [\"history\", \"selection\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"alignLeft\",\n                run: () => this.setAlignment(\"left\"),\n                isAvailable: this.canSetAlignment.bind(this),\n            },\n            {\n                id: \"alignCenter\",\n                run: () => this.setAlignment(\"center\"),\n                isAvailable: this.canSetAlignment.bind(this),\n            },\n            {\n                id: \"alignRight\",\n                run: () => this.setAlignment(\"right\"),\n                isAvailable: this.canSetAlignment.bind(this),\n            },\n            {\n                id: \"justify\",\n                run: () => this.setAlignment(\"justify\"),\n                isAvailable: this.canSetAlignment.bind(this),\n            },\n        ],\n        toolbar_items: [\n            {\n                id: \"alignment\",\n                groupId: \"layout\",\n                description: _t(\"Align text\"),\n                Component: AlignSelector,\n                props: {\n                    getItems: () => alignmentItems,\n                    getDisplay: () => this.alignment,\n                    onSelected: (item) => {\n                        this.setAlignment(item.mode);\n                    },\n                },\n                isAvailable: this.canSetAlignment.bind(this),\n            },\n        ],\n\n        /** Handlers */\n        selectionchange_handlers: this.updateAlignmentParams.bind(this),\n        post_undo_handlers: this.updateAlignmentParams.bind(this),\n        post_redo_handlers: this.updateAlignmentParams.bind(this),\n        remove_all_formats_handlers: this.setAlignment.bind(this),\n\n        /** Predicates */\n        has_format_predicates: (node) => closestBlock(node)?.style.textAlign,\n    };\n\n    setup() {\n        this.alignment = reactive({ displayName: \"\" });\n        this.canSetAlignmentMemoized = weakMemoize(\n            (selection) => isHtmlContentSupported(selection) && this.getBlocksToAlign().length > 0\n        );\n    }\n\n    get alignmentMode() {\n        const sel = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        const block = closestBlock(sel?.anchorNode);\n        const textAlign = this.getTextAlignment(block);\n        return [\"center\", \"right\", \"justify\"].includes(textAlign) ? textAlign : \"left\";\n    }\n\n    getTextAlignment(block) {\n        const { direction, textAlign } = getComputedStyle(block);\n        if (textAlign === \"start\") {\n            return direction === \"rtl\" ? \"right\" : \"left\";\n        } else if (textAlign === \"end\") {\n            return direction === \"rtl\" ? \"left\" : \"right\";\n        }\n        return textAlign;\n    }\n\n    getBlocksToAlign() {\n        return this.dependencies.selection\n            .getTargetedNodes()\n            .filter((node) => isVisibleTextNode(node) || node.nodeName === \"BR\")\n            .map((node) => closestBlock(node))\n            .filter((block) => block.isContentEditable);\n    }\n\n    setAlignment(mode = \"\") {\n        const visitedBlocks = new Set();\n        let isAlignmentUpdated = false;\n\n        for (const block of this.getBlocksToAlign()) {\n            if (!visitedBlocks.has(block)) {\n                const currentTextAlign = this.getTextAlignment(block);\n                if (currentTextAlign !== mode) {\n                    block.style.textAlign = mode;\n                    isAlignmentUpdated = true;\n                }\n                visitedBlocks.add(block);\n            }\n        }\n        if (mode && isAlignmentUpdated) {\n            this.dependencies.history.addStep();\n        }\n        this.updateAlignmentParams();\n    }\n\n    canSetAlignment(selection) {\n        return this.canSetAlignmentMemoized(selection);\n    }\n\n    updateAlignmentParams() {\n        this.alignment.displayName = this.alignmentMode;\n    }\n}\n", "import { Component, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nexport class AlignSelector extends Component {\n    static template = \"html_editor.AlignSelector\";\n    static props = {\n        getItems: Function,\n        getDisplay: Function,\n        onSelected: Function,\n        ...toolbarButtonProps,\n    };\n    static components = { Dropdown, DropdownItem };\n\n    setup() {\n        this.items = this.props.getItems();\n        this.state = useState(this.props.getDisplay());\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n    }\n\n    onSelected(item) {\n        this.props.onSelected(item);\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { fillShrunkPhrasingParent } from \"@html_editor/utils/dom\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { parseHTML } from \"@html_editor/utils/html\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { htmlEscape } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { isParagraphRelatedElement } from \"../utils/dom_info\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nfunction isAvailable(selection) {\n    return (\n        isHtmlContentSupported(selection) &&\n        !closestElement(selection.anchorNode, \".o_editor_banner\")\n    );\n}\n\n/**\n * @typedef { Object } BannerShared\n * @property { BannerPlugin['insertBanner'] } insertBanner\n */\n\nexport class BannerPlugin extends Plugin {\n    static id = \"banner\";\n    // sanitize plugin is required to handle `contenteditable` attribute.\n    static dependencies = [\"baseContainer\", \"history\", \"dom\", \"emoji\", \"selection\", \"sanitize\"];\n    static shared = [\"insertBanner\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"banner_info\",\n                title: _t(\"Banner Info\"),\n                description: _t(\"Insert an info banner\"),\n                icon: \"fa-info-circle\",\n                isAvailable,\n                run: () => {\n                    this.insertBanner(_t(\"Banner Info\"), \"\ud83d\udca1\", \"info\");\n                },\n            },\n            {\n                id: \"banner_success\",\n                title: _t(\"Banner Success\"),\n                description: _t(\"Insert a success banner\"),\n                icon: \"fa-check-circle\",\n                isAvailable,\n                run: () => {\n                    this.insertBanner(_t(\"Banner Success\"), \"\u2705\", \"success\");\n                },\n            },\n            {\n                id: \"banner_warning\",\n                title: _t(\"Banner Warning\"),\n                description: _t(\"Insert a warning banner\"),\n                icon: \"fa-exclamation-triangle\",\n                isAvailable,\n                run: () => {\n                    this.insertBanner(_t(\"Banner Warning\"), \"\u26a0\ufe0f\", \"warning\");\n                },\n            },\n            {\n                id: \"banner_danger\",\n                title: _t(\"Banner Danger\"),\n                description: _t(\"Insert a danger banner\"),\n                icon: \"fa-exclamation-circle\",\n                isAvailable,\n                run: () => {\n                    this.insertBanner(_t(\"Banner Danger\"), \"\u274c\", \"danger\");\n                },\n            },\n        ],\n        powerbox_categories: withSequence(20, { id: \"banner\", name: _t(\"Banner\") }),\n        powerbox_items: [\n            {\n                commandId: \"banner_info\",\n                categoryId: \"banner\",\n            },\n            {\n                commandId: \"banner_success\",\n                categoryId: \"banner\",\n            },\n            {\n                commandId: \"banner_warning\",\n                categoryId: \"banner\",\n            },\n            {\n                commandId: \"banner_danger\",\n                categoryId: \"banner\",\n            },\n        ],\n        power_buttons_visibility_predicates: ({ anchorNode }) =>\n            !closestElement(anchorNode, \".o_editor_banner\"),\n        move_node_blacklist_selectors: \".o_editor_banner *\",\n        move_node_whitelist_selectors: \".o_editor_banner\",\n    };\n\n    setup() {\n        this.addDomListener(this.editable, \"click\", (e) => {\n            if (e.target.classList.contains(\"o_editor_banner_icon\")) {\n                this.onBannerEmojiChange(e.target);\n            }\n        });\n    }\n\n    insertBanner(title, emoji, alertClass, containerClass = \"\", contentClass = \"\") {\n        containerClass = containerClass ? `${containerClass} ` : \"\";\n        contentClass = contentClass ? `${contentClass} ` : \"\";\n\n        const selection = this.dependencies.selection.getEditableSelection();\n        const blockEl = closestBlock(selection.anchorNode);\n        let baseContainer;\n        if (isParagraphRelatedElement(blockEl)) {\n            baseContainer = this.document.createElement(blockEl.nodeName);\n            baseContainer.append(...blockEl.childNodes);\n        } else if (blockEl.nodeName === \"LI\") {\n            baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            baseContainer.append(...blockEl.childNodes);\n            fillShrunkPhrasingParent(blockEl);\n        } else {\n            baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            fillShrunkPhrasingParent(baseContainer);\n        }\n        const baseContainerHtml = baseContainer.outerHTML;\n        const bannerElement = parseHTML(\n            this.document,\n            `<div class=\"${containerClass}o_editor_banner user-select-none o-contenteditable-false lh-1 d-flex align-items-center alert alert-${alertClass} pb-0 pt-3\" data-oe-role=\"status\">\n                <i class=\"o_editor_banner_icon mb-3 fst-normal\" data-oe-aria-label=\"${htmlEscape(\n                    title\n                )}\">${emoji}</i>\n                <div class=\"${contentClass}o_editor_banner_content o-contenteditable-true w-100 px-3\">\n                    ${baseContainerHtml}\n                </div>\n            </div>`\n        ).childNodes[0];\n        this.dependencies.dom.insert(bannerElement);\n        this.dependencies.selection.setCursorEnd(\n            bannerElement.querySelector(`.o_editor_banner_content > ${baseContainer.tagName}`)\n        );\n        // Remove the old element.\n        if (bannerElement.nextSibling?.nodeName === blockEl.nodeName) {\n            bannerElement.nextSibling.remove();\n        }\n        this.dependencies.history.addStep();\n    }\n\n    onBannerEmojiChange(iconElement) {\n        this.dependencies.emoji.showEmojiPicker({\n            target: iconElement,\n            onSelect: (emoji) => {\n                iconElement.textContent = emoji;\n                this.dependencies.history.addStep();\n            },\n        });\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { Component, useState, onWillDestroy, status, markup } from \"@odoo/owl\";\n\nconst POSTPROCESS_GENERATED_CONTENT = (content, baseContainer) => {\n    let lines = content.split(\"\\n\");\n    if (baseContainer.toUpperCase() === \"P\") {\n        // P has a margin bottom which is used as an interline, no need to\n        // keep empty lines in that case.\n        lines = lines.filter((line) => line.trim().length);\n    }\n    const fragment = document.createDocumentFragment();\n    let parentUl, parentOl;\n    let lineIndex = 0;\n    for (const line of lines) {\n        if (line.trim().startsWith(\"- \")) {\n            // Create or continue an unordered list.\n            parentUl = parentUl || document.createElement(\"ul\");\n            const li = document.createElement(\"li\");\n            li.innerText = line.trim().slice(2);\n            parentUl.appendChild(li);\n        } else if (\n            (parentOl && line.startsWith(`${parentOl.children.length + 1}. `)) ||\n            (!parentOl && line.startsWith(\"1. \") && lines[lineIndex + 1]?.startsWith(\"2. \"))\n        ) {\n            // Create or continue an ordered list (only if the line starts\n            // with the next number in the current ordered list (or 1 if no\n            // ordered list was in progress and it's followed by a 2).\n            parentOl = parentOl || document.createElement(\"ol\");\n            const li = document.createElement(\"li\");\n            li.innerText = line.slice(line.indexOf(\".\") + 2);\n            parentOl.appendChild(li);\n        } else if (line.trim().length === 0) {\n            const emptyLine = document.createElement(\"DIV\");\n            emptyLine.append(document.createElement(\"BR\"));\n            fragment.appendChild(emptyLine);\n        } else {\n            // Insert any list in progress, and a new block for the current\n            // line.\n            [parentUl, parentOl].forEach((list) => list && fragment.appendChild(list));\n            parentUl = parentOl = undefined;\n            const block = document.createElement(line.startsWith(\"Title: \") ? \"h2\" : baseContainer);\n            block.innerText = line;\n            fragment.appendChild(block);\n        }\n        lineIndex += 1;\n    }\n    [parentUl, parentOl].forEach((list) => list && fragment.appendChild(list));\n    return fragment;\n};\n\nexport class ChatGPTDialog extends Component {\n    static template = \"\";\n    static components = { Dialog };\n    static props = {\n        insert: { type: Function },\n        close: { type: Function },\n        sanitize: { type: Function },\n        baseContainer: { type: String, optional: true },\n    };\n    static defaultProps = {\n        baseContainer: \"DIV\",\n    };\n\n    setup() {\n        this.notificationService = useService(\"notification\");\n        this.state = useState({ selectedMessageId: null });\n        onWillDestroy(() => this.pendingRpcPromise?.abort());\n    }\n\n    selectMessage(ev) {\n        this.state.selectedMessageId = +ev.currentTarget.getAttribute(\"data-message-id\");\n    }\n\n    insertMessage(ev) {\n        this.selectMessage(ev);\n        this._confirm();\n    }\n\n    formatContent(content) {\n        const fragment = POSTPROCESS_GENERATED_CONTENT(content, this.props.baseContainer);\n        let result = \"\";\n        for (const child of fragment.children) {\n            this.props.sanitize(child, { IN_PLACE: true });\n            result += child.outerHTML;\n        }\n        return markup(result);\n    }\n\n    generate(prompt, callback) {\n        const protectedCallback = (...args) => {\n            if (status(this) !== \"destroyed\") {\n                delete this.pendingRpcPromise;\n                return callback(...args);\n            }\n        };\n        this.pendingRpcPromise = rpc(\n            \"/html_editor/generate_text\",\n            {\n                prompt,\n                conversation_history: this.state.conversationHistory,\n            },\n            { silent: true }\n        );\n        return this.pendingRpcPromise\n            .then((content) => protectedCallback(content))\n            .catch((error) => protectedCallback(_t(error.data?.message || error.message), true));\n    }\n\n    _cancel() {\n        this.props.close();\n    }\n\n    _confirm() {\n        try {\n            this.props.close();\n            const text = this.state.messages.find(\n                (message) => message.id === this.state.selectedMessageId\n            )?.text;\n            this.notificationService.add(_t(\"Your content was successfully generated.\"), {\n                title: _t(\"Content generated\"),\n                type: \"success\",\n            });\n            const fragment = POSTPROCESS_GENERATED_CONTENT(text || \"\", this.props.baseContainer);\n            this.props.sanitize(fragment, { IN_PLACE: true });\n            this.props.insert(fragment);\n        } catch (e) {\n            this.props.close();\n            throw e;\n        }\n    }\n}\n", "import { useState } from \"@odoo/owl\";\nimport { ChatGPTDialog } from \"./chatgpt_dialog\";\n\nexport class ChatGPTTranslateDialog extends ChatGPTDialog {\n    static template = \"html_editor.ChatGPTTranslateDialog\";\n    static props = {\n        ...super.props,\n        originalText: String,\n        language: String,\n    };\n\n    setup() {\n        super.setup();\n        this.state = useState({\n            ...this.state,\n            conversationHistory: [\n                {\n                    role: \"system\",\n                    content:\n                        \"You are a translation assistant. You goal is to translate text while maintaining the original format and\" +\n                        \"respecting specific instructions. \\n\" +\n                        \"Instructions: \\n\" +\n                        \"- You must respect the format (wrapping the translated text between <generated_text> and </generated_text>)\\n\" +\n                        \"- Do not write HTML.\",\n                },\n            ],\n            messages: [],\n            translationInProgress: true,\n        });\n        this.translate();\n    }\n\n    async translate() {\n        const query = `Translate <generated_text>${this.props.originalText}</generated_text> to ${this.props.language}`;\n        const messageId = new Date().getTime();\n        await this.generate(query, (content, isError) => {\n            let translatedText = content\n                .replace(/^[\\s\\S]*<generated_text>/, \"\")\n                .replace(/<\\/generated_text>[\\s\\S]*$/, \"\");\n            if (!this.formatContent(translatedText).length) {\n                isError = true;\n                translatedText = \"You didn't select any text.\";\n            }\n            this.state.translationInProgress = false;\n            if (!isError) {\n                // There was no error, add the response to the history.\n                this.state.conversationHistory.push(\n                    {\n                        role: \"user\",\n                        content: query,\n                    },\n                    {\n                        role: \"assistant\",\n                        content,\n                    }\n                );\n            }\n            this.state.messages.push({\n                author: \"assistant\",\n                text: translatedText,\n                id: messageId,\n                isError,\n            });\n            this.state.selectedMessageId = messageId;\n        });\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { ChatGPTTranslateDialog } from \"@html_editor/main/chatgpt/chatgpt_translate_dialog\";\nimport { LanguageSelector } from \"@html_editor/main/chatgpt/language_selector\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { user } from \"@web/core/user\";\nimport { isContentEditable } from \"@html_editor/utils/dom_info\";\n\nexport class ChatGPTTranslatePlugin extends Plugin {\n    static id = \"chatgpt_translate\";\n    static dependencies = [\n        \"baseContainer\",\n        \"selection\",\n        \"history\",\n        \"dom\",\n        \"sanitize\",\n        \"dialog\",\n        \"split\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        toolbar_groups: withSequence(50, {\n            id: \"ai\",\n        }),\n        toolbar_items: [\n            {\n                id: \"translate\",\n                groupId: \"ai\",\n                description: _t(\"Translate with AI\"),\n                isAvailable: (selection) => !selection.isCollapsed && user.userId,\n                isDisabled: this.isNotReplaceableByAI.bind(this),\n                Component: LanguageSelector,\n                props: {\n                    onSelected: (language) => this.openDialog({ language }),\n                },\n            },\n        ],\n    };\n\n    isNotReplaceableByAI(selection = this.dependencies.selection.getEditableSelection()) {\n        const isEmpty = !selection.textContent().replace(/\\s+/g, \"\");\n        const cannotReplace = this.dependencies.selection\n            .getTargetedNodes()\n            .find((el) => this.dependencies.split.isUnsplittable(el) || !isContentEditable(el));\n        return cannotReplace || isEmpty;\n    }\n\n    openDialog(params = {}) {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const dialogParams = {\n            insert: (content) => {\n                const insertedNodes = this.dependencies.dom.insert(content);\n                this.dependencies.history.addStep();\n                // Add a frame around the inserted content to highlight it for 2\n                // seconds.\n                const start = insertedNodes?.length && closestElement(insertedNodes[0]);\n                const end =\n                    insertedNodes?.length &&\n                    closestElement(insertedNodes[insertedNodes.length - 1]);\n                if (start && end) {\n                    const divContainer = this.editable.parentElement;\n                    let [parent, left, top] = [\n                        start.offsetParent,\n                        start.offsetLeft,\n                        start.offsetTop - start.scrollTop,\n                    ];\n                    while (parent && !parent.contains(divContainer)) {\n                        left += parent.offsetLeft;\n                        top += parent.offsetTop - parent.scrollTop;\n                        parent = parent.offsetParent;\n                    }\n                    let [endParent, endTop] = [end.offsetParent, end.offsetTop - end.scrollTop];\n                    while (endParent && !endParent.contains(divContainer)) {\n                        endTop += endParent.offsetTop - endParent.scrollTop;\n                        endParent = endParent.offsetParent;\n                    }\n                    const div = document.createElement(\"div\");\n                    div.classList.add(\"o-chatgpt-content\");\n                    const FRAME_PADDING = 3;\n                    div.style.left = `${left - FRAME_PADDING}px`;\n                    div.style.top = `${top - FRAME_PADDING}px`;\n                    div.style.width = `${\n                        Math.max(start.offsetWidth, end.offsetWidth) + FRAME_PADDING * 2\n                    }px`;\n                    div.style.height = `${endTop + end.offsetHeight - top + FRAME_PADDING * 2}px`;\n                    divContainer.prepend(div);\n                    setTimeout(() => div.remove(), 2000);\n                }\n            },\n            ...params,\n        };\n        dialogParams.baseContainer = this.dependencies.baseContainer.getDefaultNodeName();\n        // collapse to end\n        const sanitize = this.dependencies.sanitize.sanitize;\n        const originalText = selection.textContent() || \"\";\n        this.dependencies.dialog.addDialog(ChatGPTTranslateDialog, {\n            ...dialogParams,\n            originalText,\n            sanitize,\n        });\n        if (this.services.ui.isSmall) {\n            // TODO: Find a better way and avoid modifying range\n            // HACK: In the case of opening through dropdown:\n            // - when dropdown open, it keep the element focused before the open\n            // - when opening the dialog through the dropdown, the dropdown closes\n            // - upon close, the generic code of the dropdown sets focus on the kept element (in our case, the editable)\n            // - we need to remove the range after the generic code of the dropdown is triggered so we hack it by removing the range in the next tick\n            Promise.resolve().then(() => {\n                // If the dialog is opened on a small screen, remove all selection\n                // because the selection can be seen through the dialog on some devices.\n                this.document.getSelection()?.removeAllRanges();\n            });\n        }\n    }\n}\n", "import { Component, onWillStart, useState } from \"@odoo/owl\";\nimport { useChildRef, useService } from \"@web/core/utils/hooks\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { loadLanguages } from \"@web/core/l10n/translation\";\nimport { jsToPyLocale } from \"@web/core/l10n/utils\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { user } from \"@web/core/user\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\n\nexport class LanguageSelector extends Component {\n    static template = \"html_editor.LanguageSelector\";\n    static props = {\n        ...toolbarButtonProps,\n        onSelected: { type: Function },\n    };\n    static components = { Dropdown, DropdownItem };\n\n    setup() {\n        this.orm = useService(\"orm\");\n        this.state = useState({\n            languages: [],\n        });\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n        onWillStart(() => {\n            if (user.userId) {\n                const userLang = jsToPyLocale(user.lang);\n                loadLanguages(this.orm).then((res) => {\n                    const userLangIndex = res.findIndex((lang) => lang[0] === userLang);\n                    if (userLangIndex !== -1) {\n                        const [userLangItem] = res.splice(userLangIndex, 1);\n                        res.unshift(userLangItem);\n                    }\n                    this.state.languages = res;\n                });\n            }\n        });\n    }\n    onSelected(language) {\n        this.props.onSelected(language);\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { unwrapContents } from \"@html_editor/utils/dom\";\nimport { closestElement, firstLeaf } from \"@html_editor/utils/dom_traversal\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nconst REGEX_BOOTSTRAP_COLUMN = /(?:^| )col(-[a-zA-Z]+)?(-\\d+)?(?= |$)/;\n\nfunction isUnremovableColumn(node, root) {\n    const isColumnInnerStructure =\n        node.nodeName === \"DIV\" && [...node.classList].some((cls) => /^row$|^col$|^col-/.test(cls));\n\n    if (!isColumnInnerStructure) {\n        return false;\n    }\n    if (!root) {\n        return true;\n    }\n    const closestColumnContainer = closestElement(node, \"div.o_text_columns\");\n    return !root.contains(closestColumnContainer);\n}\n\nfunction columnIsAvailable(numberOfColumns) {\n    return (selection) => {\n        const row = closestElement(selection.anchorNode, \".o_text_columns .row\");\n        return !(row && row.childElementCount === numberOfColumns);\n    };\n}\n\nexport class ColumnPlugin extends Plugin {\n    static id = \"column\";\n    static dependencies = [\"baseContainer\", \"selection\", \"history\", \"dom\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"columnize\",\n                title: _t(\"Columnize\"),\n                description: _t(\"Convert into columns\"),\n                icon: \"fa-columns\",\n                run: this.columnize.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        powerbox_items: [\n            {\n                title: _t(\"2 columns\"),\n                description: _t(\"Convert into 2 columns\"),\n                categoryId: \"structure\",\n                isAvailable: columnIsAvailable(2),\n                commandId: \"columnize\",\n                commandParams: 2,\n            },\n            {\n                title: _t(\"3 columns\"),\n                description: _t(\"Convert into 3 columns\"),\n                categoryId: \"structure\",\n                isAvailable: columnIsAvailable(3),\n                commandId: \"columnize\",\n                commandParams: 3,\n            },\n            {\n                title: _t(\"4 columns\"),\n                description: _t(\"Convert into 4 columns\"),\n                categoryId: \"structure\",\n                isAvailable: columnIsAvailable(4),\n                commandId: \"columnize\",\n                commandParams: 4,\n            },\n            {\n                title: _t(\"Remove columns\"),\n                description: _t(\"Back to one column\"),\n                categoryId: \"structure\",\n                isAvailable: (selection) =>\n                    !!closestElement(selection.anchorNode, \".o_text_columns .row\"),\n                commandId: \"columnize\",\n                commandParams: 0,\n            },\n        ],\n        hints: [\n            {\n                selector: `.odoo-editor-editable .o_text_columns div[class^='col-'],\n                            .odoo-editor-editable .o_text_columns div[class^='col-']>${baseContainerGlobalSelector}:first-child`,\n                text: _t(\"Empty column\"),\n            },\n        ],\n        unremovable_node_predicates: isUnremovableColumn,\n        power_buttons_visibility_predicates: ({ anchorNode }) =>\n            !closestElement(anchorNode, \".o_text_columns\"),\n        move_node_whitelist_selectors: \".o_text_columns\",\n        move_node_blacklist_selectors: \".o_text_columns *\",\n        hint_targets_providers: (selectionData) => {\n            if (!selectionData.documentSelection) {\n                return [];\n            }\n            const anchorNode = selectionData.documentSelection.anchorNode;\n            const columnContainer = closestElement(anchorNode, \"div.o_text_columns\");\n            if (!columnContainer) {\n                return [];\n            }\n            const closestColumn = closestElement(anchorNode, \"div[class^='col-']\");\n            const closestBlockEl = closestBlock(anchorNode);\n            return [...columnContainer.querySelectorAll(\"div[class^='col-']\")]\n                .map((column) => {\n                    const block = closestBlock(firstLeaf(column));\n                    return column === closestColumn && block !== closestBlockEl ? null : block;\n                })\n                .filter(Boolean);\n        },\n    };\n\n    columnize(numberOfColumns) {\n        const selectionToRestore = this.dependencies.selection.getEditableSelection();\n        const anchor = selectionToRestore.anchorNode;\n        const hasColumns = !!closestElement(anchor, \".o_text_columns\");\n        if (hasColumns) {\n            if (numberOfColumns) {\n                this.changeColumnsNumber(anchor, numberOfColumns);\n            } else {\n                this.removeColumns(anchor);\n            }\n        } else if (numberOfColumns) {\n            this.createColumns(anchor, numberOfColumns);\n        }\n        this.dependencies.selection.setSelection(selectionToRestore);\n        this.dependencies.history.addStep();\n    }\n\n    removeColumns(anchor) {\n        const container = closestElement(anchor, \".o_text_columns\");\n        const rows = unwrapContents(container);\n        for (const row of rows) {\n            const columns = unwrapContents(row);\n            for (const column of columns) {\n                unwrapContents(column);\n                // const columnContents = unwrapContents(column);\n                // for (const node of columnContents) {\n                //     resetOuids(node);\n                // }\n            }\n        }\n    }\n\n    createColumns(anchor, numberOfColumns) {\n        const container = this.document.createElement(\"div\");\n        if (!closestElement(anchor, \".container\")) {\n            container.classList.add(\"container\");\n        }\n        container.classList.add(\"o_text_columns\", \"o-contenteditable-false\");\n        const row = this.document.createElement(\"div\");\n        row.classList.add(\"row\");\n        container.append(row);\n        const block = closestBlock(anchor);\n        // resetOuids(block);\n        const columnSize = Math.floor(12 / numberOfColumns);\n        const columns = [];\n        for (let i = 0; i < numberOfColumns; i++) {\n            const column = this.document.createElement(\"div\");\n            column.classList.add(`col-${columnSize}`, \"o-contenteditable-true\");\n            row.append(column);\n            columns.push(column);\n        }\n        columns.shift().append(block);\n        for (const column of columns) {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            baseContainer.append(this.document.createElement(\"br\"));\n            column.append(baseContainer);\n        }\n        this.dependencies.dom.insert(container);\n    }\n\n    changeColumnsNumber(anchor, numberOfColumns) {\n        const row = closestElement(anchor, \".row\");\n        const columns = [...row.children];\n        const columnSize = Math.floor(12 / numberOfColumns);\n        const diff = numberOfColumns - columns.length;\n        if (!diff) {\n            return;\n        }\n        for (const column of columns) {\n            column.className = column.className.replace(\n                REGEX_BOOTSTRAP_COLUMN,\n                `col$1-${columnSize}`\n            );\n        }\n        if (diff > 0) {\n            // Add extra columns.\n            let lastColumn = columns[columns.length - 1];\n            for (let i = 0; i < diff; i++) {\n                const column = this.document.createElement(\"div\");\n                column.classList.add(`col-${columnSize}`, \"o-contenteditable-true\");\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                baseContainer.append(this.document.createElement(\"br\"));\n                column.append(baseContainer);\n                lastColumn.after(column);\n                lastColumn = column;\n            }\n        } else if (diff < 0) {\n            // Remove superfluous columns.\n            const contents = [];\n            for (let i = diff; i < 0; i++) {\n                const column = columns.pop();\n                const columnContents = unwrapContents(column);\n                // for (const node of columnContents) {\n                //     resetOuids(node);\n                // }\n                contents.unshift(...columnContents);\n            }\n            columns[columns.length - 1].append(...contents);\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { EmojiPicker } from \"@web/core/emoji_picker/emoji_picker\";\nimport { _t } from \"@web/core/l10n/translation\";\n\n/**\n * @typedef { Object } EmojiShared\n * @property { EmojiPlugin['showEmojiPicker'] } showEmojiPicker\n */\n\nexport class EmojiPlugin extends Plugin {\n    static id = \"emoji\";\n    static dependencies = [\"history\", \"overlay\", \"dom\", \"selection\"];\n    static shared = [\"showEmojiPicker\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"addEmoji\",\n                title: _t(\"Emoji\"),\n                description: _t(\"Add an emoji\"),\n                icon: \"fa-smile-o\",\n                run: this.showEmojiPicker.bind(this),\n            },\n        ],\n        powerbox_items: [\n            {\n                categoryId: \"widget\",\n                commandId: \"addEmoji\",\n            },\n        ],\n    };\n\n    setup() {\n        this.overlay = this.dependencies.overlay.createOverlay(EmojiPicker, {\n            hasAutofocus: true,\n            className: \"popover\",\n        });\n    }\n\n    /**\n     * @param {Object} options\n     * @param {HTMLElement} options.target - The target element to position the overlay.\n     * @param {Function} [options.onSelect] - The callback function to handle the selection of an emoji.\n     * If not provided, the emoji will be inserted into the editor and a step will be trigerred.\n     */\n    showEmojiPicker({ target, onSelect } = {}) {\n        this.overlay.open({\n            props: {\n                close: () => {\n                    this.overlay.close();\n                    this.dependencies.selection.focusEditable();\n                },\n                onSelect: (str) => {\n                    if (onSelect) {\n                        onSelect(str);\n                        return;\n                    }\n                    this.dependencies.dom.insert(str);\n                    this.dependencies.history.addStep();\n                },\n            },\n            target,\n        });\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { cleanTextNode } from \"@html_editor/utils/dom\";\nimport { isTextNode, isZwnbsp } from \"@html_editor/utils/dom_info\";\nimport { prepareUpdate } from \"@html_editor/utils/dom_state\";\nimport { descendants, selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { leftPos, rightPos } from \"@html_editor/utils/position\";\nimport { callbacksForCursorUpdate } from \"@html_editor/utils/selection\";\n\n/** @typedef {import(\"../core/selection_plugin\").Cursors} Cursors */\n\n/**\n * @typedef { Object } FeffShared\n * @property { FeffPlugin['addFeff'] } addFeff\n * @property { FeffPlugin['removeFeffs'] } removeFeffs\n */\n\n/**\n * @typedef {((node: Node) => boolean)[]} legit_feff_predicates\n * @typedef {((root: EditorContext[\"editable\"], cursors: Cursors) => Node[])[]} feff_providers\n * @typedef {(() => string)[]} selectors_for_feff_providers\n */\n\n/**\n * This plugin manages the insertion and removal of the zero-width no-break\n * space character (U+FEFF). These characters enable the user to place the\n * cursor in positions that would otherwise not be easy or possible, such as\n * between two contenteditable=false elements, or at the end (but inside) of a\n * link.\n */\nexport class FeffPlugin extends Plugin {\n    static id = \"feff\";\n    static dependencies = [\"selection\"];\n    static shared = [\"addFeff\", \"removeFeffs\", \"surroundWithFeffs\"];\n\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        normalize_handlers: this.updateFeffs.bind(this),\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        intangible_char_for_keyboard_navigation_predicates: (ev, char, lastSkipped) =>\n            // Skip first FEFF, but not the second one (unless shift is pressed).\n            char === \"\\uFEFF\" && (ev.shiftKey || lastSkipped !== \"\\uFEFF\"),\n        clipboard_content_processors: this.processContentForClipboard.bind(this),\n        clipboard_text_processors: (text) => text.replace(/\\ufeff/g, \"\"),\n    };\n\n    cleanForSave({ root, preserveSelection = false }) {\n        if (preserveSelection) {\n            const cursors = this.getCursors();\n            this.removeFeffs(root, cursors);\n            cursors.restore();\n        } else {\n            this.removeFeffs(root, null);\n        }\n    }\n\n    /**\n     * @param {Element} root\n     * @param {Cursors} [cursors]\n     * @param {Object} [options]\n     */\n    removeFeffs(root, cursors, { exclude = () => false } = {}) {\n        const hasFeff = (node) => isTextNode(node) && node.textContent.includes(\"\\ufeff\");\n        const isEditable = (node) => node.parentElement.isContentEditable;\n        const composedFilter = (node) => hasFeff(node) && isEditable(node) && !exclude(node);\n\n        for (const node of descendants(root).filter(composedFilter)) {\n            // Remove all FEFF within a `prepareUpdate` to make sure to make <br>\n            // nodes visible if needed.\n            const restoreSpaces = prepareUpdate(...leftPos(node), ...rightPos(node));\n            cleanTextNode(node, \"\\ufeff\", cursors);\n            restoreSpaces();\n        }\n    }\n\n    /**\n     * @param {Element} element\n     * @param {'before'|'after'|'prepend'|'append'} position\n     * @param {Cursors} [cursors]\n     * @returns {Node}\n     */\n    addFeff(element, position, cursors) {\n        const feff = this.document.createTextNode(\"\\ufeff\");\n        cursors?.update(callbacksForCursorUpdate[position](element, feff));\n        element[position](feff);\n        return feff;\n    }\n\n    surroundWithFeffs(node, cursors) {\n        const addFeff = (position) => {\n            // skip cursor update for append, we want to keep it before\n            // the added FEFF\n            const c = position === \"append\" ? null : cursors;\n            return this.addFeff(node, position, c);\n        };\n\n        const zwnbspNodes = [];\n        for (const [position, relation] of [\n            [\"before\", \"previousSibling\"],\n            [\"after\", \"nextSibling\"],\n            [\"prepend\", \"firstChild\"],\n            [\"append\", \"lastChild\"],\n        ]) {\n            const candidate = node[relation];\n            const feff =\n                isZwnbsp(candidate) && !zwnbspNodes.includes(candidate)\n                    ? candidate\n                    : addFeff(position);\n            zwnbspNodes.push(feff);\n        }\n        return zwnbspNodes;\n    }\n\n    /**\n     * Adds a FEFF before and after each element that matches the selectors\n     * provided by the registered providers.\n     *\n     * @param {Element} root\n     * @param {Cursors} cursors\n     * @returns {Node[]}\n     */\n    padWithFeffs(root, cursors) {\n        const combinedSelector = this.getResource(\"selectors_for_feff_providers\")\n            .map((provider) => provider())\n            .join(\", \");\n        if (!combinedSelector) {\n            return [];\n        }\n        const elements = [...selectElements(root, combinedSelector)];\n        const isEditable = (node) => node.parentElement?.isContentEditable;\n        const feffNodes = elements\n            .filter(isEditable)\n            .flatMap((el) => {\n                const addFeff = (position) => this.addFeff(el, position, cursors);\n                return [\n                    isZwnbsp(el.previousSibling) ? el.previousSibling : addFeff(\"before\"),\n                    isZwnbsp(el.nextSibling) ? el.nextSibling : addFeff(\"after\"),\n                ];\n            })\n            // Avoid sequential FEFFs\n            .filter((feff, i, array) => !(i > 0 && areCloseSiblings(array[i - 1], feff)));\n        return feffNodes;\n    }\n\n    updateFeffs(root) {\n        const cursors = this.getCursors();\n        // Pad based on selectors\n        const feffNodesBasedOnSelectors = this.padWithFeffs(root, cursors);\n        // Custom feff adding\n        // Each provider is responsible for adding (or keeping) FEFF nodes and\n        // returning a list of them.\n        const customFeffNodes = this.getResource(\"feff_providers\").flatMap((p) => p(root, cursors));\n        const feffNodesToKeep = new Set([...feffNodesBasedOnSelectors, ...customFeffNodes]);\n        this.removeFeffs(root, cursors, {\n            exclude: (node) =>\n                feffNodesToKeep.has(node) ||\n                this.getResource(\"legit_feff_predicates\").some((predicate) => predicate(node)),\n        });\n        cursors.restore();\n    }\n\n    /**\n     * Retuns a patched version of cursors in which `restore` does nothing\n     * unless `update` has been called at least once.\n     */\n    getCursors() {\n        const cursors = this.dependencies.selection.preserveSelection();\n        const originalUpdate = cursors.update.bind(cursors);\n        const originalRestore = cursors.restore.bind(cursors);\n        let shouldRestore = false;\n        cursors.update = (...args) => {\n            shouldRestore = true;\n            return originalUpdate(...args);\n        };\n        cursors.restore = () => {\n            if (shouldRestore) {\n                originalRestore();\n            }\n        };\n        return cursors;\n    }\n\n    processContentForClipboard(clonedContent) {\n        descendants(clonedContent)\n            .filter(isTextNode)\n            .filter((node) => node.textContent.includes(\"\\ufeff\"))\n            .forEach((node) => (node.textContent = node.textContent.replace(/\\ufeff/g, \"\")));\n        return clonedContent;\n    }\n}\n\n/**\n * Whether two nodes are consecutive siblings, ignoring empty text nodes between\n * them.\n *\n * @param {Node} a\n * @param {Node} b\n */\nfunction areCloseSiblings(a, b) {\n    let next = a.nextSibling;\n    // skip empty text nodes\n    while (next && isTextNode(next) && !next.textContent) {\n        next = next.nextSibling;\n    }\n    return next === b;\n}\n", "import { Component, useState } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { applyOpacityToGradient, isColorGradient } from \"@web/core/utils/colors\";\nimport { GradientPicker } from \"./gradient_picker/gradient_picker\";\n\nconst DEFAULT_GRADIENT_COLORS = [\n    \"linear-gradient(135deg, rgb(255, 204, 51) 0%, rgb(226, 51, 255) 100%)\",\n    \"linear-gradient(135deg, rgb(102, 153, 255) 0%, rgb(255, 51, 102) 100%)\",\n    \"linear-gradient(135deg, rgb(47, 128, 237) 0%, rgb(178, 255, 218) 100%)\",\n    \"linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)\",\n    \"linear-gradient(135deg, rgb(214, 255, 127) 0%, rgb(0, 179, 204) 100%)\",\n    \"linear-gradient(135deg, rgb(255, 222, 69) 0%, rgb(69, 33, 0) 100%)\",\n    \"linear-gradient(135deg, rgb(222, 222, 222) 0%, rgb(69, 69, 69) 100%)\",\n    \"linear-gradient(135deg, rgb(255, 222, 202) 0%, rgb(202, 115, 69) 100%)\",\n];\n\nexport class ColorPickerGradientTab extends Component {\n    static template = \"html_editor.ColorPickerGradientTab\";\n    static components = { GradientPicker };\n    static props = {\n        applyColor: Function,\n        onColorClick: Function,\n        onColorPreview: Function,\n        onColorPointerOver: Function,\n        onColorPointerOut: Function,\n        onFocusin: Function,\n        onFocusout: Function,\n        setOnCloseCallback: { type: Function, optional: true },\n        setOperationCallbacks: { type: Function, optional: true },\n        defaultOpacity: { type: Number, optional: true },\n        noTransparency: { type: Boolean, optional: true },\n        selectedColor: { type: String, optional: true },\n        \"*\": { optional: true },\n    };\n    setup() {\n        this.state = useState({\n            showGradientPicker: false,\n        });\n        this.applyOpacityToGradient = applyOpacityToGradient;\n        this.DEFAULT_GRADIENT_COLORS = DEFAULT_GRADIENT_COLORS;\n    }\n\n    getCurrentGradientColor() {\n        if (isColorGradient(this.props.selectedColor)) {\n            return this.props.selectedColor;\n        }\n    }\n\n    toggleGradientPicker() {\n        this.state.showGradientPicker = !this.state.showGradientPicker;\n    }\n}\n\nregistry.category(\"color_picker_tabs\").add(\n    \"html_editor.gradient\",\n    {\n        id: \"gradient\",\n        name: _t(\"Gradient\"),\n        component: ColorPickerGradientTab,\n    },\n    { sequence: 60 }\n);\n", "import { Plugin } from \"@html_editor/plugin\";\nimport {\n    BG_CLASSES_REGEX,\n    COLOR_COMBINATION_CLASSES_REGEX,\n    hasAnyNodesColor,\n    hasColor,\n    TEXT_CLASSES_REGEX,\n    hasTextColorClass,\n} from \"@html_editor/utils/color\";\nimport { fillEmpty, unwrapContents } from \"@html_editor/utils/dom\";\nimport {\n    isEmptyBlock,\n    isRedundantElement,\n    isTextNode,\n    isWhitespace,\n    isZwnbsp,\n} from \"@html_editor/utils/dom_info\";\nimport { closestElement, descendants, selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { isColorGradient, rgbaToHex } from \"@web/core/utils/colors\";\nimport { backgroundImageCssToParts, backgroundImagePartsToCss } from \"@html_editor/utils/image\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { isBlock } from \"@html_editor/utils/blocks\";\n\nconst COLOR_COMBINATION_CLASSES = [1, 2, 3, 4, 5].map((i) => `o_cc${i}`);\nconst COLOR_COMBINATION_SELECTOR = COLOR_COMBINATION_CLASSES.map((c) => `.${c}`).join(\", \");\n\n/**\n * @typedef { Object } ColorShared\n * @property { ColorPlugin['colorElement'] } colorElement\n * @property { ColorPlugin['removeAllColor'] } removeAllColor\n * @property { ColorPlugin['getElementColors'] } getElementColors\n * @property { ColorPlugin['applyColor'] } applyColor\n */\n\n/**\n * @typedef {((element: HTMLElement, cssProp: string, color: string) => boolean)[]} apply_color_style_overrides\n * @typedef {((color: string, mode: \"color\" | \"backgroundColor\") => void)[]} color_apply_overrides\n * @typedef {((color: string, mode: \"color\" | \"backgroundColor\") => string)[]} apply_background_color_processors\n * @typedef {((color: string) => string)[]} get_background_color_processors\n *\n * @typedef {((el: HTMLElement, actionParam: string) => string)[]} color_combination_getters\n */\n\nexport class ColorPlugin extends Plugin {\n    static id = \"color\";\n    static dependencies = [\"selection\", \"split\", \"history\", \"format\"];\n    static shared = [\n        \"colorElement\",\n        \"removeAllColor\",\n        \"getElementColors\",\n        \"getColorCombination\",\n        \"applyColor\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"applyColor\",\n                run: ({ color, mode }) => {\n                    this.applyColor(color, mode);\n                    this.dependencies.history.addStep();\n                },\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        /** Handlers */\n        remove_all_formats_handlers: this.removeAllColor.bind(this),\n        color_combination_getters: getColorCombinationFromClass,\n\n        /** Predicates */\n        has_format_predicates: [\n            (node) => hasColor(closestElement(node), \"color\"),\n            (node) => hasColor(closestElement(node), \"backgroundColor\"),\n        ],\n        format_class_predicates: (className) =>\n            TEXT_CLASSES_REGEX.test(className) || BG_CLASSES_REGEX.test(className),\n        normalize_handlers: this.normalize.bind(this),\n    };\n\n    normalize(root) {\n        for (const el of selectElements(root, \"font\")) {\n            if (isRedundantElement(el)) {\n                unwrapContents(el);\n            }\n        }\n    }\n\n    getElementColors(el) {\n        const elStyle = getComputedStyle(el);\n        const backgroundImage = elStyle.backgroundImage;\n        const gradient = backgroundImageCssToParts(backgroundImage).gradient;\n        const hasGradient = isColorGradient(gradient);\n        const hasTextGradientClass = el.classList.contains(\"text-gradient\");\n\n        let backgroundColor = elStyle.backgroundColor;\n        for (const processor of this.getResource(\"get_background_color_processors\")) {\n            backgroundColor = processor(backgroundColor);\n        }\n\n        return {\n            color: hasGradient && hasTextGradientClass ? gradient : rgbaToHex(elStyle.color),\n            backgroundColor:\n                hasGradient && !hasTextGradientClass ? gradient : rgbaToHex(backgroundColor),\n        };\n    }\n\n    removeAllColor() {\n        const colorModes = [\"color\", \"backgroundColor\"];\n        let someColorWasRemoved = true;\n        while (someColorWasRemoved) {\n            someColorWasRemoved = false;\n            for (const mode of colorModes) {\n                let max = 40;\n                const hasAnySelectedNodeColor = (mode) => {\n                    const nodes = this.dependencies.selection\n                        .getTargetedNodes()\n                        .filter(\n                            (n) =>\n                                isTextNode(n) ||\n                                (mode === \"backgroundColor\" &&\n                                    n.classList.contains(\"o_selected_td\"))\n                        );\n                    return hasAnyNodesColor(nodes, mode);\n                };\n                while (hasAnySelectedNodeColor(mode) && max > 0) {\n                    this.applyColor(\"\", mode);\n                    someColorWasRemoved = true;\n                    max--;\n                }\n                if (max === 0) {\n                    someColorWasRemoved = false;\n                    throw new Error(\"Infinite Loop in removeAllColor().\");\n                }\n            }\n        }\n    }\n\n    /**\n     * Apply a css or class color on the current selection (wrapped in <font>).\n     *\n     * @param {string} color hexadecimal or bg-name/text-name class\n     * @param {string} mode 'color' or 'backgroundColor'\n     * @param {boolean} [previewMode=false] true - apply color in preview mode\n     */\n    applyColor(color, mode, previewMode = false) {\n        this.dependencies.selection.selectAroundNonEditable();\n        if (mode === \"backgroundColor\") {\n            for (const processor of this.getResource(\"apply_background_color_processors\")) {\n                color = processor(color, mode);\n            }\n        }\n        if (this.delegateTo(\"color_apply_overrides\", color, mode, previewMode)) {\n            return;\n        }\n        let selection = this.dependencies.selection.getEditableSelection();\n        let targetedNodes;\n        // Get the <font> nodes to color\n        if (selection.isCollapsed) {\n            let zws;\n            if (\n                selection.anchorNode.nodeType !== Node.TEXT_NODE &&\n                selection.anchorNode.textContent !== \"\\u200b\"\n            ) {\n                zws = selection.anchorNode;\n            } else {\n                zws = this.dependencies.format.insertAndSelectZws();\n            }\n            selection = this.dependencies.selection.setSelection(\n                {\n                    anchorNode: zws,\n                    anchorOffset: 0,\n                },\n                { normalize: false }\n            );\n            targetedNodes = [zws];\n        } else {\n            selection = this.dependencies.split.splitSelection();\n            targetedNodes = this.dependencies.selection\n                .getTargetedNodes()\n                .filter(\n                    (node) =>\n                        this.dependencies.selection.isNodeEditable(node) && node.nodeName !== \"T\"\n                );\n            if (isEmptyBlock(selection.endContainer)) {\n                targetedNodes.push(selection.endContainer, ...descendants(selection.endContainer));\n            }\n        }\n\n        const findTopMostDecoration = (current) => {\n            const decoration = closestElement(current.parentNode, \"s, u\");\n            return decoration?.textContent === current.textContent\n                ? findTopMostDecoration(decoration)\n                : current;\n        };\n\n        const hexColor = rgbaToHex(color).toLowerCase();\n        const selectedNodes = targetedNodes\n            .filter((node) => {\n                if (mode === \"backgroundColor\" && color) {\n                    return !closestElement(node, \"table.o_selected_table\");\n                }\n                if (closestElement(node).classList.contains(\"o_default_color\")) {\n                    return false;\n                }\n                const li = closestElement(node, \"li\");\n                if (li && color && this.dependencies.selection.areNodeContentsFullySelected(li)) {\n                    return rgbaToHex(li.style.color).toLowerCase() !== hexColor;\n                }\n                return true;\n            })\n            .map((node) => findTopMostDecoration(node));\n\n        const targetedFieldNodes = new Set(\n            this.dependencies.selection\n                .getTargetedNodes()\n                .map((n) => closestElement(n, \"*[t-field],*[t-out],*[t-esc]\"))\n                .filter(Boolean)\n        );\n\n        const getFonts = (selectedNodes) =>\n            selectedNodes.flatMap((node) => {\n                // Invisible nodes like `feff`s can be removed during `splitAroundUntil`\n                // so we filter them out.\n                if (!node.isConnected) {\n                    return [];\n                }\n                let font =\n                    closestElement(node, \"font\") ||\n                    closestElement(\n                        node,\n                        '[style*=\"color\"]:not(li), [style*=\"background-color\"]:not(li), [style*=\"background-image\"]:not(li)'\n                    ) ||\n                    closestElement(node, \"span\") ||\n                    closestElement(node, (node) => hasTextColorClass(node, mode));\n\n                const faNodes = font?.querySelectorAll(\".fa\");\n                if (faNodes && Array.from(faNodes).some((faNode) => faNode.contains(node))) {\n                    return font;\n                }\n                const children = font && descendants(font);\n                const hasInlineGradient = font && isColorGradient(font.style[\"background-image\"]);\n                const isFullySelected =\n                    children && children.every((child) => selectedNodes.includes(child));\n                const isTextGradient =\n                    hasInlineGradient && font.classList.contains(\"text-gradient\");\n                const shouldReplaceExistingGradient =\n                    isFullySelected &&\n                    ((mode === \"color\" && isTextGradient) ||\n                        (mode === \"backgroundColor\" && !isTextGradient));\n                if (\n                    font &&\n                    font.nodeName !== \"T\" &&\n                    (font.nodeName !== \"SPAN\" ||\n                        font.style[mode] ||\n                        font.style.backgroundImage ||\n                        hasTextColorClass(font, mode)) &&\n                    (isColorGradient(color) ||\n                        color === \"\" ||\n                        !hasInlineGradient ||\n                        shouldReplaceExistingGradient) &&\n                    !this.dependencies.split.isUnsplittable(font)\n                ) {\n                    // Partially selected <font>: split it.\n                    const selectedChildren = children.filter((child) =>\n                        selectedNodes.includes(child)\n                    );\n                    if (selectedChildren.length) {\n                        if (isBlock(font)) {\n                            const colorStyles = [\"color\", \"background-color\", \"background-image\"];\n                            const newFont = this.document.createElement(\"font\");\n                            for (const style of colorStyles) {\n                                const styleValue = font.style[style];\n                                if (styleValue) {\n                                    this.colorElement(newFont, styleValue, style);\n                                    font.style.removeProperty(style);\n                                }\n                            }\n                            font.classList.forEach((className) => {\n                                if (TEXT_CLASSES_REGEX.test(className)) {\n                                    font.classList.remove(className);\n                                    newFont.classList.add(className);\n                                }\n                            });\n                            newFont.append(...font.childNodes);\n                            font.append(newFont);\n                            font = newFont;\n                        }\n                        const closestGradientEl = closestElement(\n                            node,\n                            'font[style*=\"background-image\"], span[style*=\"background-image\"]'\n                        );\n                        const isGradientBeingUpdated = closestGradientEl && isColorGradient(color);\n                        const splitnode = isGradientBeingUpdated ? closestGradientEl : font;\n                        const cursors = this.dependencies.selection.preserveSelection();\n                        font = this.dependencies.split.splitAroundUntil(\n                            selectedChildren,\n                            splitnode,\n                            cursors\n                        );\n                        cursors.restore();\n                        if (isGradientBeingUpdated) {\n                            const classRegex =\n                                mode === \"color\" ? TEXT_CLASSES_REGEX : BG_CLASSES_REGEX;\n                            // When updating a gradient, remove color applied to\n                            // its descendants.This ensures the gradient remains\n                            // visible without being overwritten by a descendant's color.\n                            for (const node of descendants(font)) {\n                                if (\n                                    node.nodeType === Node.ELEMENT_NODE &&\n                                    (node.style[mode] || classRegex.test(node.className))\n                                ) {\n                                    this.colorElement(node, \"\", mode);\n                                    node.style.webkitTextFillColor = \"\";\n                                    if (!node.getAttribute(\"style\")) {\n                                        unwrapContents(node);\n                                    }\n                                }\n                            }\n                        } else if (\n                            mode === \"color\" &&\n                            (font.style.webkitTextFillColor ||\n                                (closestGradientEl &&\n                                    closestGradientEl.classList.contains(\"text-gradient\") &&\n                                    !shouldReplaceExistingGradient))\n                        ) {\n                            font.style.webkitTextFillColor = color;\n                        }\n                    } else {\n                        font = [];\n                    }\n                } else if (\n                    (node.nodeType === Node.TEXT_NODE && !isZwnbsp(node)) ||\n                    (node.nodeName === \"BR\" && isEmptyBlock(node.parentNode)) ||\n                    (node.nodeType === Node.ELEMENT_NODE &&\n                        [\"inline\", \"inline-block\"].includes(getComputedStyle(node).display) &&\n                        !isWhitespace(node.textContent) &&\n                        !node.classList.contains(\"btn\") &&\n                        !node.querySelector(\"font\") &&\n                        node.nodeName !== \"A\" &&\n                        !(node.nodeName === \"SPAN\" && node.style[\"fontSize\"]))\n                ) {\n                    // Node is a visible text or inline node without font nor a button:\n                    // wrap it in a <font>.\n                    const previous = node.previousSibling;\n                    const classRegex = mode === \"color\" ? BG_CLASSES_REGEX : TEXT_CLASSES_REGEX;\n                    if (\n                        previous &&\n                        previous.nodeName === \"FONT\" &&\n                        !previous.style[mode === \"color\" ? \"backgroundColor\" : \"color\"] &&\n                        !classRegex.test(previous.className) &&\n                        selectedNodes.includes(previous.firstChild) &&\n                        selectedNodes.includes(previous.lastChild)\n                    ) {\n                        // Directly follows a fully selected <font> that isn't\n                        // colored in the other mode: append to that.\n                        font = previous;\n                    } else {\n                        // No <font> found: insert a new one.\n                        font = this.document.createElement(\"font\");\n                        node.after(font);\n                        if (isTextGradient && mode === \"color\") {\n                            font.style.webkitTextFillColor = color;\n                        }\n                    }\n                    if (node.textContent) {\n                        font.appendChild(node);\n                    } else {\n                        fillEmpty(font);\n                    }\n                } else {\n                    font = []; // Ignore non-text or invisible text nodes.\n                }\n                return font;\n            });\n\n        for (const fieldNode of targetedFieldNodes) {\n            this.colorElement(fieldNode, color, mode);\n        }\n\n        let fonts = getFonts(selectedNodes);\n        // Dirty fix as the previous call could have unconnected elements\n        // because of the `splitAroundUntil`. Another call should provide he\n        // correct list of fonts.\n        if (!fonts.every((font) => font.isConnected)) {\n            fonts = getFonts(selectedNodes);\n        }\n\n        // Color the selected <font>s and remove uncolored fonts.\n        const fontsSet = new Set(fonts);\n        for (const font of fontsSet) {\n            this.colorElement(font, color, mode);\n            if (\n                !hasColor(font, \"color\") &&\n                !hasColor(font, \"backgroundColor\") &&\n                [\"FONT\", \"SPAN\"].includes(font.nodeName) &&\n                (!font.hasAttribute(\"style\") || !color)\n            ) {\n                for (const child of [...font.childNodes]) {\n                    font.parentNode.insertBefore(child, font);\n                }\n                font.parentNode.removeChild(font);\n                fontsSet.delete(font);\n            }\n        }\n        this.dependencies.selection.setSelection(selection, { normalize: false });\n    }\n\n    /**\n     * Applies a css or class color (fore- or background-) to an element.\n     * Replace the color that was already there if any.\n     *\n     * @param {Element} element\n     * @param {string} color hexadecimal or bg-name/text-name class\n     * @param {'color'|'backgroundColor'} mode 'color' or 'backgroundColor'\n     */\n    colorElement(element, color, mode) {\n        let parts = backgroundImageCssToParts(element.style[\"background-image\"]);\n        const oldClassName = element.getAttribute(\"class\") || \"\";\n\n        if (element.matches(COLOR_COMBINATION_SELECTOR)) {\n            removePresetGradient(element);\n        }\n\n        const hasGradientStyle = element.style.backgroundImage.includes(\"-gradient\");\n        if (mode === \"backgroundColor\") {\n            if (!color) {\n                element.classList.remove(\"o_cc\", ...COLOR_COMBINATION_CLASSES);\n            }\n            const hasGradient = getComputedStyle(element).backgroundImage.includes(\"-gradient\");\n            delete parts.gradient;\n            let newBackgroundImage = backgroundImagePartsToCss(parts);\n            // we override the bg image if the new bg image is empty, but the previous one is a gradient.\n            if (hasGradient && !newBackgroundImage) {\n                newBackgroundImage = \"none\";\n            }\n            element.style.backgroundImage = newBackgroundImage;\n            element.style[\"background-color\"] = \"\";\n        }\n\n        const newClassName = oldClassName\n            .replace(mode === \"color\" ? TEXT_CLASSES_REGEX : BG_CLASSES_REGEX, \"\")\n            .replace(/\\btext-gradient\\b/g, \"\") // cannot be combined with setting a background\n            .replace(/\\s+/, \" \");\n        if (oldClassName !== newClassName) {\n            element.setAttribute(\"class\", newClassName);\n        }\n        if (color.startsWith(\"text\") || color.startsWith(\"bg-\")) {\n            element.style[mode] = \"\";\n            element.classList.add(color);\n        } else if (isColorGradient(color)) {\n            element.style[mode] = \"\";\n            parts.gradient = color;\n            if (mode === \"color\") {\n                element.style[\"background-color\"] = \"\";\n                element.classList.add(\"text-gradient\");\n            }\n            this.applyColorStyle(element, \"background-image\", backgroundImagePartsToCss(parts));\n        } else {\n            delete parts.gradient;\n            if (hasGradientStyle && !backgroundImagePartsToCss(parts)) {\n                element.style[\"background-image\"] = \"\";\n            }\n            // Change camelCase to kebab-case.\n            mode = mode.replace(\"backgroundColor\", \"background-color\");\n            this.applyColorStyle(element, mode, color);\n        }\n\n        // It was decided that applying a color combination removes any \"color\"\n        // value (custom color, color classes, gradients, ...). Changing any\n        // \"color\", including color combinations, should still not remove the\n        // other background layers though (image, video, shape, ...).\n        if (color.startsWith(\"o_cc\")) {\n            parts = backgroundImageCssToParts(element.style[\"background-image\"]);\n            element.classList.remove(...COLOR_COMBINATION_CLASSES);\n            element.classList.add(\"o_cc\", color);\n\n            const hasBackgroundColor = !!getComputedStyle(element).backgroundColor;\n            const hasGradient = getComputedStyle(element).backgroundImage.includes(\"-gradient\");\n            const backgroundImage = element.style[\"background-image\"];\n            // Override gradient background image if coming from css rather than inline style.\n            if (hasBackgroundColor && hasGradient && !backgroundImage) {\n                element.style.backgroundImage = \"none\";\n            }\n        }\n\n        this.fixColorCombination(element, color);\n    }\n    /**\n     * There is a limitation with css. The defining a background image and a\n     * background gradient is done only by setting one style (background-image).\n     * If there is a class (in this case o_cc[1-5]) that defines a gradient, it\n     * will be overridden by the background-image property.\n     *\n     * This function will set the gradient of the o_cc in the background-image\n     * so that setting an image in the background-image property will not\n     * override the gradient.\n     */\n    fixColorCombination(element, color) {\n        const parts = backgroundImageCssToParts(element.style[\"background-image\"]);\n        const hasBackgroundColor =\n            element.style[\"background-color\"] ||\n            !!element.className.match(/\\bbg-/) ||\n            parts.gradient;\n\n        if (!hasBackgroundColor && (isColorGradient(color) || color.startsWith(\"o_cc\"))) {\n            element.style[\"background-image\"] = \"\";\n            parts.gradient = backgroundImageCssToParts(\n                // Compute the style from o_cc class.\n                getComputedStyle(element).backgroundImage\n            ).gradient;\n            element.style[\"background-image\"] = backgroundImagePartsToCss(parts);\n        }\n    }\n\n    getColorCombination(el, actionParam) {\n        for (const handler of this.getResource(\"color_combination_getters\")) {\n            const value = handler(el, actionParam);\n            if (value) {\n                return value;\n            }\n        }\n    }\n\n    /**\n     * @param {Element} element\n     * @param {string} cssProp\n     * @param {string} cssValue\n     */\n    applyColorStyle(element, mode, color) {\n        if (this.delegateTo(\"apply_color_style_overrides\", element, mode, color)) {\n            return;\n        }\n        element.style[mode] = color;\n    }\n}\n\nfunction getColorCombinationFromClass(el) {\n    return el.className.match?.(COLOR_COMBINATION_CLASSES_REGEX)?.[0];\n}\n\n/**\n * Remove the gradient of the element only if it is the inheritance from the o_cc selector.\n */\nfunction removePresetGradient(element) {\n    const oldBackgroundImage = element.style[\"background-image\"];\n    const parts = backgroundImageCssToParts(oldBackgroundImage);\n    const currentGradient = parts.gradient;\n    element.style.removeProperty(\"background-image\");\n    const styleWithoutGradient = getComputedStyle(element);\n    const presetGradient = backgroundImageCssToParts(styleWithoutGradient.backgroundImage).gradient;\n    if (presetGradient !== currentGradient) {\n        const withGradient = backgroundImagePartsToCss(parts);\n        element.style[\"background-image\"] = withGradient === \"none\" ? \"\" : withGradient;\n    } else {\n        delete parts.gradient;\n        const withoutGradient = backgroundImagePartsToCss(parts);\n        element.style[\"background-image\"] = withoutGradient === \"none\" ? \"\" : withoutGradient;\n    }\n}\n", "import { isColorGradient } from \"@web/core/utils/colors\";\nimport { Component, useState } from \"@odoo/owl\";\nimport {\n    useColorPicker,\n    DEFAULT_COLORS,\n    DEFAULT_THEME_COLOR_VARS,\n} from \"@web/core/color_picker/color_picker\";\nimport { effect } from \"@web/core/utils/reactive\";\nimport { toolbarButtonProps } from \"../toolbar/toolbar\";\nimport { getCSSVariableValue, getHtmlStyle } from \"@html_editor/utils/formatting\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\n\nexport class ColorSelector extends Component {\n    static template = \"html_editor.ColorSelector\";\n    static props = {\n        ...toolbarButtonProps,\n        mode: { type: String },\n        type: { type: String },\n        getSelectedColors: Function,\n        applyColor: Function,\n        applyColorPreview: Function,\n        applyColorResetPreview: Function,\n        getUsedCustomColors: Function,\n        getTargetedElements: Function,\n        colorPrefix: { type: String },\n        enabledTabs: { type: Array, optional: true },\n        cssVarColorPrefix: { type: String, optional: true },\n        onClose: Function,\n    };\n    static defaultProps = {\n        cssVarColorPrefix: \"\",\n        enabledTabs: [\"solid\", \"gradient\", \"custom\"],\n    };\n\n    setup() {\n        this.state = useState({});\n        const htmlStyle = getHtmlStyle(document);\n        const defaultThemeColors = DEFAULT_THEME_COLOR_VARS.map((color) =>\n            getCSSVariableValue(color, htmlStyle)\n        );\n        this.solidColors = [\n            ...DEFAULT_COLORS.flat(),\n            ...defaultThemeColors,\n            getCSSVariableValue(\"body-color\", htmlStyle), // Default applied color\n            \"#00000000\", //Default Background color\n        ];\n        effect(\n            (selectedColors) => {\n                this.state.selectedColor = selectedColors[this.props.mode];\n                this.state.defaultTab = \"solid\";\n                this.state.selectedTab = this.getCorrespondingColorTab(\n                    selectedColors[this.props.mode]\n                );\n                this.state.getTargetedElements = this.props.getTargetedElements;\n                this.state.mode = this.props.mode;\n            },\n            [this.props.getSelectedColors()]\n        );\n\n        const colorPickerRef = useChildRef();\n        this.colorPicker = useColorPicker(\n            \"root\",\n            {\n                state: this.state,\n                applyColor: this.props.applyColor,\n                applyColorPreview: this.props.applyColorPreview,\n                applyColorResetPreview: this.props.applyColorResetPreview,\n                getUsedCustomColors: this.props.getUsedCustomColors,\n                colorPrefix: this.props.colorPrefix,\n                enabledTabs: this.props.enabledTabs,\n                cssVarColorPrefix: this.props.cssVarColorPrefix,\n            },\n            {\n                env: this.__owl__.childEnv,\n                onClose: () => {\n                    this.props.applyColorResetPreview();\n                    this.props.onClose();\n                },\n                ref: colorPickerRef,\n            }\n        );\n        useDropdownAutoVisibility(this.env.overlayState, colorPickerRef);\n    }\n\n    getCorrespondingColorTab(color) {\n        if (!color || this.solidColors.includes(color.toUpperCase())) {\n            return \"solid\";\n        } else if (isColorGradient(color)) {\n            return \"gradient\";\n        } else {\n            return \"custom\";\n        }\n    }\n\n    getSelectedColorStyle() {\n        if (isColorGradient(this.state.selectedColor)) {\n            return `border-bottom: 2px solid transparent; border-image: ${this.state.selectedColor}; border-image-slice: 1`;\n        }\n        return `border-bottom: 2px solid ${this.state.selectedColor}`;\n    }\n}\n", "import { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { ColorSelector } from \"./color_selector\";\nimport { reactive } from \"@odoo/owl\";\nimport { isTextNode } from \"@html_editor/utils/dom_info\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { isCSSColor, RGBA_REGEX, rgbaToHex } from \"@web/core/utils/colors\";\n\nconst RGBA_OPACITY = 0.6;\nconst HEX_OPACITY = \"99\";\n\n/**\n * @typedef { Object } ColorUIShared\n * @property { ColorUIPlugin['getPropsForColorSelector'] } getPropsForColorSelector\n */\n\nexport class ColorUIPlugin extends Plugin {\n    static id = \"colorUi\";\n    static dependencies = [\"color\", \"history\", \"selection\"];\n    static shared = [\"getPropsForColorSelector\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        toolbar_items: [\n            {\n                id: \"forecolor\",\n                groupId: \"decoration\",\n                namespaces: [\"compact\", \"expanded\"],\n                description: _t(\"Apply Font Color\"),\n                Component: ColorSelector,\n                props: this.getPropsForColorSelector(\"foreground\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"backcolor\",\n                groupId: \"decoration\",\n                description: _t(\"Apply Background Color\"),\n                Component: ColorSelector,\n                props: this.getPropsForColorSelector(\"background\"),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        selectionchange_handlers: this.updateSelectedColor.bind(this),\n        get_background_color_processors: this.getBackgroundColorProcessor.bind(this),\n        apply_background_color_processors: this.applyBackgroundColorProcessor.bind(this),\n    };\n\n    setup() {\n        this.selectedColors = reactive({ color: \"\", backgroundColor: \"\" });\n        this.previewableApplyColor = this.dependencies.history.makePreviewableOperation(\n            (color, mode, previewMode) =>\n                this.dependencies.color.applyColor(color, mode, previewMode)\n        );\n    }\n\n    /**\n     * @param {'foreground'|'background'} type\n     */\n    getPropsForColorSelector(type) {\n        const mode = type === \"foreground\" ? \"color\" : \"backgroundColor\";\n        return {\n            type,\n            mode,\n\n            getUsedCustomColors: () => this.getUsedCustomColors(mode),\n            getSelectedColors: () => this.selectedColors,\n            applyColor: (color) => this.applyColorCommit({ color, mode }),\n            applyColorPreview: (color) => this.applyColorPreview({ color, mode }),\n            applyColorResetPreview: this.applyColorResetPreview.bind(this),\n            colorPrefix: mode === \"color\" ? \"text-\" : \"bg-\",\n            onClose: () => this.dependencies.selection.focusEditable(),\n            getTargetedElements: () => {\n                const nodes = this.dependencies.selection.getTargetedNodes().filter(isTextNode);\n                return nodes.map((node) => closestElement(node));\n            },\n        };\n    }\n\n    /**\n     * Apply a css or class color on the current selection (wrapped in <font>).\n     *\n     * @param {Object} param\n     * @param {string} param.color hexadecimal or bg-name/text-name class\n     * @param {string} param.mode 'color' or 'backgroundColor'\n     */\n    applyColorCommit({ color, mode }) {\n        this.previewableApplyColor.commit(color, mode);\n        this.updateSelectedColor();\n    }\n    /**\n     * Apply a css or class color on the current selection (wrapped in <font>)\n     * in preview mode so that it can be reset.\n     *\n     * @param {Object} param\n     * @param {string} param.color hexadecimal or bg-name/text-name class\n     * @param {string} param.mode 'color' or 'backgroundColor'\n     */\n    applyColorPreview({ color, mode }) {\n        // Preview the color before applying it.\n        this.previewableApplyColor.preview(color, mode, true);\n        this.updateSelectedColor();\n    }\n    /**\n     * Reset the color applied in preview mode.\n     */\n    applyColorResetPreview() {\n        this.previewableApplyColor.revert();\n        this.updateSelectedColor();\n    }\n\n    getUsedCustomColors(mode) {\n        const allFont = this.editable.querySelectorAll(\"font\");\n        const usedCustomColors = new Set();\n        for (const font of allFont) {\n            if (isCSSColor(font.style[mode])) {\n                usedCustomColors.add(rgbaToHex(font.style[mode]));\n            }\n        }\n        return usedCustomColors;\n    }\n\n    updateSelectedColor() {\n        const nodes = this.dependencies.selection.getTargetedNodes().filter(isTextNode);\n        if (nodes.length === 0) {\n            return;\n        }\n        const el = closestElement(nodes[0]);\n        if (!el) {\n            return;\n        }\n\n        Object.assign(this.selectedColors, this.dependencies.color.getElementColors(el));\n    }\n\n    getBackgroundColorProcessor(backgroundColor) {\n        const activeTab = document\n            .querySelector(\".o_font_color_selector button.active\")\n            ?.innerHTML.trim();\n        if (backgroundColor.startsWith(\"rgba\") && (!activeTab || activeTab === \"Solid\")) {\n            // Buttons in the solid tab of color selector have no\n            // opacity, hence to match selected color correctly,\n            // we need to remove applied 0.6 opacity.\n            const values = backgroundColor.match(RGBA_REGEX) || [];\n            const alpha = parseFloat(values.pop()); // Extract alpha value\n            if (alpha === RGBA_OPACITY) {\n                backgroundColor = `rgb(${values.slice(0, 3).join(\", \")})`; // Remove alpha\n            }\n        }\n        return backgroundColor;\n    }\n\n    applyBackgroundColorProcessor(brackgroundColor) {\n        const activeTab = document\n            .querySelector(\".o_font_color_selector button.active\")\n            ?.innerHTML.trim();\n        if (activeTab === \"Solid\" && brackgroundColor.startsWith(\"#\")) {\n            // Apply default transparency to selected solid tab colors in background\n            // mode to make text highlighting more usable between light and dark modes.\n            brackgroundColor += HEX_OPACITY;\n        }\n        return brackgroundColor;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { FontFamilySelector } from \"@html_editor/main/font/font_family_selector\";\nimport { reactive } from \"@odoo/owl\";\nimport { closestElement } from \"../../utils/dom_traversal\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport const defaultFontFamily = {\n    name: \"Default system font\",\n    nameShort: \"Default font\",\n    fontFamily: false,\n};\nexport const fontFamilyItems = [\n    defaultFontFamily,\n    { name: \"Arial (sans-serif)\", nameShort: \"Arial\", fontFamily: \"Arial, sans-serif\" },\n    { name: \"Verdana (sans-serif)\", nameShort: \"Verdana\", fontFamily: \"Verdana, sans-serif\" },\n    { name: \"Tahoma (sans-serif)\", nameShort: \"Tahoma\", fontFamily: \"Tahoma, sans-serif\" },\n    {\n        name: \"Trebuchet MS (sans-serif)\",\n        nameShort: \"Trebuchet MS\",\n        fontFamily: '\"Trebuchet MS\", sans-serif',\n    },\n    {\n        name: \"Courier New (monospace)\",\n        nameShort: \"Courier New\",\n        fontFamily: '\"Courier New\", monospace',\n    },\n];\n\nexport class FontFamilyPlugin extends Plugin {\n    static id = \"fontFamily\";\n    static dependencies = [\"split\", \"selection\", \"dom\", \"format\", \"font\"];\n    fontFamily = reactive({ displayName: defaultFontFamily.nameShort });\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        toolbar_items: [\n            withSequence(15, {\n                id: \"font-family\",\n                groupId: \"font\",\n                description: _t(\"Select font family\"),\n                Component: FontFamilySelector,\n                props: {\n                    fontFamilyItems: fontFamilyItems,\n                    currentFontFamily: this.fontFamily,\n                    onSelected: (item) => {\n                        this.dependencies.format.formatSelection(\"fontFamily\", {\n                            applyStyle: item.fontFamily !== false,\n                            formatProps: item,\n                        });\n                        this.fontFamily.displayName = item.nameShort;\n                    },\n                },\n                isAvailable: isHtmlContentSupported,\n            }),\n        ],\n        /** Handlers */\n        selectionchange_handlers: this.updateCurrentFontFamily.bind(this),\n        post_undo_handlers: this.updateCurrentFontFamily.bind(this),\n        post_redo_handlers: this.updateCurrentFontFamily.bind(this),\n    };\n\n    updateCurrentFontFamily(ev) {\n        const selelectionData = this.dependencies.selection.getSelectionData();\n        if (!selelectionData.documentSelectionIsInEditable) {\n            return;\n        }\n        const anchorElement = closestElement(selelectionData.editableSelection.anchorNode);\n        const anchorElementFontFamily = getComputedStyle(anchorElement).fontFamily;\n        const currentFontItem =\n            anchorElementFontFamily &&\n            fontFamilyItems.find((item) => item.fontFamily === anchorElementFontFamily);\n\n        this.fontFamily.displayName = (currentFontItem || defaultFontFamily).nameShort;\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nexport class FontFamilySelector extends Component {\n    static template = \"html_editor.FontFamilySelector\";\n    static props = {\n        document: { optional: true },\n        fontFamilyItems: Object,\n        currentFontFamily: Object,\n        onSelected: Function,\n        ...toolbarButtonProps,\n    };\n    static components = { Dropdown, DropdownItem };\n\n    setup() {\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { isBlock, closestBlock } from \"@html_editor/utils/blocks\";\nimport { unwrapContents } from \"@html_editor/utils/dom\";\nimport {\n    isParagraphRelatedElement,\n    isRedundantElement,\n    isEmptyBlock,\n    isVisibleTextNode,\n    isZWS,\n} from \"@html_editor/utils/dom_info\";\nimport {\n    ancestors,\n    childNodes,\n    closestElement,\n    createDOMPathGenerator,\n    descendants,\n    selectElements,\n} from \"@html_editor/utils/dom_traversal\";\nimport {\n    convertNumericToUnit,\n    getCSSVariableValue,\n    getHtmlStyle,\n    getFontSizeDisplayValue,\n    FONT_SIZE_CLASSES,\n} from \"@html_editor/utils/formatting\";\nimport { DIRECTIONS } from \"@html_editor/utils/position\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { FontSelector } from \"./font_selector\";\nimport {\n    getBaseContainerSelector,\n    SUPPORTED_BASE_CONTAINER_NAMES,\n} from \"@html_editor/utils/base_container\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { reactive } from \"@odoo/owl\";\nimport { FontSizeSelector } from \"./font_size_selector\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { weakMemoize } from \"@html_editor/utils/functions\";\n\n/** @typedef {import(\"plugins\").TranslatedString} TranslatedString */\n\n/**\n * @typedef {((insertedNode: Node) => insertedNode)[]} before_insert_within_pre_processors\n * @typedef {{ name: TranslatedString; tagName: string; extraClass?: string; }[]} font_items\n */\n\nexport const fontSizeItems = [\n    { variableName: \"display-1-font-size\", className: \"display-1-fs\" },\n    { variableName: \"display-2-font-size\", className: \"display-2-fs\" },\n    { variableName: \"display-3-font-size\", className: \"display-3-fs\" },\n    { variableName: \"display-4-font-size\", className: \"display-4-fs\" },\n    { variableName: \"h1-font-size\", className: \"h1-fs\" },\n    { variableName: \"h2-font-size\", className: \"h2-fs\" },\n    { variableName: \"h3-font-size\", className: \"h3-fs\" },\n    { variableName: \"h4-font-size\", className: \"h4-fs\" },\n    { variableName: \"h5-font-size\", className: \"h5-fs\" },\n    { variableName: \"h6-font-size\", className: \"h6-fs\" },\n    { variableName: \"font-size-base\", className: \"base-fs\" },\n    { variableName: \"small-font-size\", className: \"o_small-fs\" },\n];\n\nconst rightLeafOnlyNotBlockPath = createDOMPathGenerator(DIRECTIONS.RIGHT, {\n    leafOnly: true,\n    stopTraverseFunction: isBlock,\n    stopFunction: isBlock,\n});\n\nconst headingTags = [\"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\"];\nconst handledElemSelector = [...headingTags, \"PRE\", \"BLOCKQUOTE\"].join(\", \");\n\nexport class FontPlugin extends Plugin {\n    static id = \"font\";\n    static dependencies = [\n        \"baseContainer\",\n        \"input\",\n        \"split\",\n        \"selection\",\n        \"dom\",\n        \"format\",\n        \"lineBreak\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        font_items: [\n            withSequence(10, {\n                name: _t(\"Header 1 Display 1\"),\n                tagName: \"h1\",\n                extraClass: \"display-1\",\n            }),\n            ...[\n                { name: _t(\"Header 1\"), tagName: \"h1\" },\n                { name: _t(\"Header 2\"), tagName: \"h2\" },\n                { name: _t(\"Header 3\"), tagName: \"h3\" },\n                { name: _t(\"Header 4\"), tagName: \"h4\" },\n                { name: _t(\"Header 5\"), tagName: \"h5\" },\n                { name: _t(\"Header 6\"), tagName: \"h6\" },\n            ].map((item) => withSequence(20, item)),\n            withSequence(30, {\n                name: _t(\"Normal\"),\n                tagName: \"div\",\n                // for the FontSelector component\n                selector: getBaseContainerSelector(\"DIV\"),\n            }),\n            withSequence(40, { name: _t(\"Paragraph\"), tagName: \"p\" }),\n            withSequence(50, { name: _t(\"Code\"), tagName: \"pre\" }),\n            withSequence(60, { name: _t(\"Quote\"), tagName: \"blockquote\" }),\n        ],\n        user_commands: [\n            {\n                id: \"setTagHeading\",\n                run: ({ level } = {}) =>\n                    this.dependencies.dom.setBlock({ tagName: `H${level ?? 1}` }),\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n            {\n                id: \"setTagHeading1\",\n                title: _t(\"Heading 1\"),\n                description: _t(\"Big section heading\"),\n                icon: \"fa-header\",\n                run: () => this.dependencies.dom.setBlock({ tagName: \"H1\" }),\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n            {\n                id: \"setTagHeading2\",\n                title: _t(\"Heading 2\"),\n                description: _t(\"Medium section heading\"),\n                icon: \"fa-header\",\n                run: () => this.dependencies.dom.setBlock({ tagName: \"H2\" }),\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n            {\n                id: \"setTagHeading3\",\n                title: _t(\"Heading 3\"),\n                description: _t(\"Small section heading\"),\n                icon: \"fa-header\",\n                run: () => this.dependencies.dom.setBlock({ tagName: \"H3\" }),\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n            {\n                id: \"setTagParagraph\",\n                title: _t(\"Text\"),\n                description: _t(\"Paragraph block\"),\n                icon: \"fa-paragraph\",\n                run: () => {\n                    this.dependencies.dom.setBlock({\n                        tagName: this.dependencies.baseContainer.getDefaultNodeName(),\n                    });\n                },\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n            {\n                id: \"setTagQuote\",\n                title: _t(\"Quote\"),\n                description: _t(\"Add a blockquote section\"),\n                icon: \"fa-quote-right\",\n                run: () => this.dependencies.dom.setBlock({ tagName: \"blockquote\" }),\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n            {\n                id: \"setTagPre\",\n                title: _t(\"Code\"),\n                description: _t(\"Add a code section\"),\n                icon: \"fa-code\",\n                run: () => this.dependencies.dom.setBlock({ tagName: \"pre\" }),\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            },\n        ],\n        toolbar_groups: [\n            withSequence(10, {\n                id: \"font\",\n            }),\n        ],\n        toolbar_items: [\n            withSequence(10, {\n                id: \"font\",\n                groupId: \"font\",\n                namespaces: [\"compact\", \"expanded\"],\n                description: _t(\"Select font style\"),\n                Component: FontSelector,\n                props: {\n                    getItems: () => this.availableFontItems,\n                    getDisplay: () => this.font,\n                    onSelected: (item) => {\n                        this.dependencies.dom.setBlock({\n                            tagName: item.tagName,\n                            extraClass: item.extraClass,\n                        });\n                        this.updateFontSelectorParams();\n                    },\n                },\n                isAvailable: this.blockFormatIsAvailable.bind(this),\n            }),\n            withSequence(20, {\n                id: \"font-size\",\n                groupId: \"font\",\n                namespaces: [\"compact\", \"expanded\"],\n                description: _t(\"Select font size\"),\n                Component: FontSizeSelector,\n                props: {\n                    getItems: () => this.fontSizeItems,\n                    getDisplay: () => this.fontSize,\n                    onFontSizeInput: (size) => {\n                        this.dependencies.format.formatSelection(\"fontSize\", {\n                            formatProps: { size },\n                            applyStyle: true,\n                        });\n                        this.updateFontSizeSelectorParams();\n                    },\n                    onSelected: (item) => {\n                        this.dependencies.format.formatSelection(\"setFontSizeClassName\", {\n                            formatProps: { className: item.className },\n                            applyStyle: true,\n                        });\n                        this.updateFontSizeSelectorParams();\n                    },\n                    onBlur: () => this.dependencies.selection.focusEditable(),\n                    document: this.document,\n                },\n                isAvailable: isHtmlContentSupported,\n            }),\n        ],\n        powerbox_categories: withSequence(5, { id: \"format\", name: _t(\"Format\") }),\n        powerbox_items: [\n            {\n                categoryId: \"format\",\n                commandId: \"setTagHeading1\",\n                keywords: [_t(\"title\")],\n            },\n            {\n                categoryId: \"format\",\n                commandId: \"setTagHeading2\",\n                keywords: [_t(\"title\")],\n            },\n            {\n                categoryId: \"format\",\n                commandId: \"setTagHeading3\",\n                keywords: [_t(\"title\")],\n            },\n            {\n                categoryId: \"format\",\n                commandId: \"setTagParagraph\",\n            },\n            {\n                categoryId: \"format\",\n                commandId: \"setTagQuote\",\n            },\n            {\n                categoryId: \"format\",\n                commandId: \"setTagPre\",\n            },\n        ],\n        shorthands: [\n            {\n                pattern: /^#$/,\n                commandId: \"setTagHeading\",\n                commandParams: { level: 1 },\n            },\n            {\n                pattern: /^##$/,\n                commandId: \"setTagHeading\",\n                commandParams: { level: 2 },\n            },\n            {\n                pattern: /^###$/,\n                commandId: \"setTagHeading\",\n                commandParams: { level: 3 },\n            },\n            {\n                pattern: /^####$/,\n                commandId: \"setTagHeading\",\n                commandParams: { level: 4 },\n            },\n            {\n                pattern: /^#####$/,\n                commandId: \"setTagHeading\",\n                commandParams: { level: 5 },\n            },\n            {\n                pattern: /^######$/,\n                commandId: \"setTagHeading\",\n                commandParams: { level: 6 },\n            },\n            {\n                pattern: /^>$/,\n                commandId: \"setTagQuote\",\n            },\n        ],\n        hints: [\n            { selector: \"H1\", text: _t(\"Heading 1\") },\n            { selector: \"H2\", text: _t(\"Heading 2\") },\n            { selector: \"H3\", text: _t(\"Heading 3\") },\n            { selector: \"H4\", text: _t(\"Heading 4\") },\n            { selector: \"H5\", text: _t(\"Heading 5\") },\n            { selector: \"H6\", text: _t(\"Heading 6\") },\n            { selector: \"PRE\", text: _t(\"Code\") },\n            { selector: \"BLOCKQUOTE\", text: _t(\"Quote\") },\n        ],\n\n        /** Handlers */\n        selectionchange_handlers: [\n            this.updateFontSelectorParams.bind(this),\n            this.updateFontSizeSelectorParams.bind(this),\n        ],\n        post_undo_handlers: [\n            this.updateFontSelectorParams.bind(this),\n            this.updateFontSizeSelectorParams.bind(this),\n        ],\n        post_redo_handlers: [\n            this.updateFontSelectorParams.bind(this),\n            this.updateFontSizeSelectorParams.bind(this),\n        ],\n        normalize_handlers: this.normalize.bind(this),\n\n        /** Overrides */\n        split_element_block_overrides: [\n            this.handleSplitBlockHeading.bind(this),\n            this.handleSplitBlockPRE.bind(this),\n            this.handleSplitBlockquote.bind(this),\n        ],\n        delete_backward_overrides: withSequence(20, this.handleDeleteBackward.bind(this)),\n        delete_backward_word_overrides: this.handleDeleteBackward.bind(this),\n\n        /** Processors */\n        clipboard_content_processors: this.processContentForClipboard.bind(this),\n        before_insert_processors: this.handleInsertWithinPre.bind(this),\n\n        format_class_predicates: (className) =>\n            [...FONT_SIZE_CLASSES, \"o_default_font_size\"].includes(className),\n    };\n\n    setup() {\n        this.fontSize = reactive({ displayName: \"\" });\n        this.font = reactive({ displayName: \"\" });\n        this.blockFormatIsAvailableMemoized = weakMemoize(\n            (selection) => isHtmlContentSupported(selection) && this.dependencies.dom.canSetBlock()\n        );\n        this.availableFontItems = this.getResource(\"font_items\").filter(\n            ({ tagName }) =>\n                !SUPPORTED_BASE_CONTAINER_NAMES.includes(tagName.toUpperCase()) ||\n                this.config.baseContainers.includes(tagName.toUpperCase())\n        );\n    }\n\n    normalize(root) {\n        for (const el of selectElements(\n            root,\n            \"strong, b, span[style*='font-weight: bolder'], small\"\n        )) {\n            if (isRedundantElement(el)) {\n                unwrapContents(el);\n            }\n        }\n    }\n\n    get fontName() {\n        const sel = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        // if (!sel) {\n        //     return \"Normal\";\n        // }\n        const anchorNode = sel.anchorNode;\n        const block = closestBlock(anchorNode);\n        const tagName = block.tagName.toLowerCase();\n\n        const matchingItems = this.availableFontItems.filter((item) =>\n            item.selector ? block.matches(item.selector) : item.tagName === tagName\n        );\n\n        const matchingItemsWitoutExtraClass = matchingItems.filter((item) => !item.extraClass);\n\n        if (!matchingItems.length) {\n            return _t(\"Normal\");\n        }\n\n        return (\n            matchingItems.find((item) => block.classList.contains(item.extraClass)) ||\n            (matchingItemsWitoutExtraClass.length && matchingItemsWitoutExtraClass[0])\n        ).name;\n    }\n\n    get fontSizeName() {\n        const sel = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        if (!sel) {\n            return fontSizeItems[0].name;\n        }\n        return Math.round(getFontSizeDisplayValue(sel, this.document));\n    }\n\n    get fontSizeItems() {\n        const style = getHtmlStyle(this.document);\n        const nameAlreadyUsed = new Set();\n        return fontSizeItems\n            .flatMap((item) => {\n                const strValue = getCSSVariableValue(item.variableName, style);\n                if (!strValue) {\n                    return [];\n                }\n                const remValue = parseFloat(strValue);\n                const pxValue = convertNumericToUnit(remValue, \"rem\", \"px\", style);\n                const roundedValue = Math.round(pxValue);\n                if (nameAlreadyUsed.has(roundedValue)) {\n                    return [];\n                }\n                nameAlreadyUsed.add(roundedValue);\n\n                return [{ ...item, tagName: \"span\", name: roundedValue }];\n            })\n            .sort((a, b) => a.name - b.name);\n    }\n\n    blockFormatIsAvailable(selection) {\n        return this.blockFormatIsAvailableMemoized(selection);\n    }\n\n    // @todo @phoenix: Move this to a specific Pre/CodeBlock plugin?\n    /**\n     * Specific behavior for pre: insert newline (\\n) in text or insert p at\n     * end.\n     */\n    handleSplitBlockPRE({ targetNode, targetOffset }) {\n        const closestPre = closestElement(targetNode, \"pre\");\n        const closestBlockNode = closestBlock(targetNode);\n        if (\n            !closestPre ||\n            (closestBlockNode.nodeName !== \"PRE\" &&\n                ((closestBlockNode.textContent && !isZWS(closestBlockNode)) ||\n                    closestBlockNode.nextSibling))\n        ) {\n            return;\n        }\n\n        // Nodes to the right of the split position.\n        const nodesAfterTarget = [...rightLeafOnlyNotBlockPath(targetNode, targetOffset)];\n        if (\n            !nodesAfterTarget.length ||\n            (nodesAfterTarget.length === 1 && nodesAfterTarget[0].nodeName === \"BR\") ||\n            isEmptyBlock(closestBlockNode)\n        ) {\n            // Remove the last empty block node within pre tag\n            const [beforeElement, afterElement] = this.dependencies.split.splitElementBlock({\n                targetNode,\n                targetOffset,\n                blockToSplit: closestBlockNode,\n            });\n            const isPreBlock = beforeElement.nodeName === \"PRE\";\n            const baseContainer = isPreBlock\n                ? this.dependencies.baseContainer.createBaseContainer()\n                : afterElement;\n            if (isPreBlock) {\n                baseContainer.replaceChildren(...afterElement.childNodes);\n                afterElement.replaceWith(baseContainer);\n            } else {\n                beforeElement.remove();\n                closestPre.after(afterElement);\n            }\n            const dir = closestBlockNode.getAttribute(\"dir\") || closestPre.getAttribute(\"dir\");\n            if (dir) {\n                baseContainer.setAttribute(\"dir\", dir);\n            }\n            this.dependencies.selection.setCursorStart(baseContainer);\n        } else {\n            const lineBreak = this.document.createElement(\"br\");\n            targetNode.insertBefore(lineBreak, targetNode.childNodes[targetOffset]);\n            this.dependencies.selection.setCursorEnd(lineBreak);\n        }\n        return true;\n    }\n\n    /**\n     * Specific behavior for blockquote: insert p at end and remove the last\n     * empty node.\n     */\n    handleSplitBlockquote({ targetNode, targetOffset, blockToSplit }) {\n        const closestQuote = closestElement(targetNode, \"blockquote\");\n        const closestBlockNode = closestBlock(targetNode);\n        const blockQuotedir = closestQuote && closestQuote.getAttribute(\"dir\");\n\n        if (!closestQuote || closestBlockNode.nodeName !== \"BLOCKQUOTE\") {\n            // If the closestBlockNode is the last element child of its parent\n            // and the parent is a blockquote\n            // we should move the current block ouside of the blockquote.\n            if (\n                closestBlockNode.parentElement === closestQuote &&\n                closestBlockNode.parentElement.lastElementChild === closestBlockNode &&\n                !closestBlockNode.textContent\n            ) {\n                closestQuote.after(closestBlockNode);\n\n                if (blockQuotedir && !closestBlockNode.getAttribute(\"dir\")) {\n                    closestBlockNode.setAttribute(\"dir\", blockQuotedir);\n                }\n                this.dependencies.selection.setSelection({\n                    anchorNode: closestBlockNode,\n                    anchorOffset: 0,\n                });\n                return true;\n            }\n            return;\n        }\n\n        const selection = this.dependencies.selection.getEditableSelection();\n        const previousElementSibling = selection.anchorNode?.childNodes[selection.anchorOffset - 1];\n        const nextElementSibling = selection.anchorNode?.childNodes[selection.anchorOffset];\n        // Double enter at the end of blockquote => we should break out of the blockquote element.\n        if (previousElementSibling?.tagName === \"BR\" && nextElementSibling?.tagName === \"BR\") {\n            nextElementSibling.remove();\n            previousElementSibling.remove();\n            this.dependencies.split.splitElementBlock({\n                targetNode,\n                targetOffset,\n                blockToSplit,\n            });\n            this.dependencies.dom.setBlock({\n                tagName: this.dependencies.baseContainer.getDefaultNodeName(),\n            });\n            return true;\n        }\n\n        this.dependencies.lineBreak.insertLineBreakElement({ targetNode, targetOffset });\n        return true;\n    }\n\n    // @todo @phoenix: Move this to a specific Heading plugin?\n    /**\n     * Specific behavior for headings: do not split in two if cursor at the end but\n     * instead create a paragraph.\n     * Cursor end of line: <h1>title[]</h1> + ENTER <=> <h1>title</h1><p>[]<br/></p>\n     * Cursor in the line: <h1>tit[]le</h1> + ENTER <=> <h1>tit</h1><h1>[]le</h1>\n     */\n    handleSplitBlockHeading(params) {\n        const closestHeading = closestElement(params.targetNode, (element) =>\n            headingTags.includes(element.tagName)\n        );\n        if (closestHeading) {\n            const [, newElement] = this.dependencies.split.splitElementBlock(params);\n            // @todo @phoenix: if this condition can be anticipated before the split,\n            // handle the splitBlock only in such case.\n            if (\n                newElement &&\n                headingTags.includes(newElement.tagName) &&\n                !descendants(newElement).some(isVisibleTextNode)\n            ) {\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                const dir = newElement.getAttribute(\"dir\");\n                if (dir) {\n                    baseContainer.setAttribute(\"dir\", dir);\n                }\n                baseContainer.replaceChildren(...newElement.childNodes);\n                newElement.replaceWith(baseContainer);\n                this.dependencies.selection.setCursorStart(baseContainer);\n            }\n            return true;\n        }\n    }\n\n    /**\n     * Transform an empty heading or pre at the beginning of the\n     * editable into a base container. An empty blockquote is transformed\n     * into a base container, regardless of its position in the editable.\n     */\n    handleDeleteBackward({ startContainer, startOffset, endContainer, endOffset }) {\n        // Detect if cursor is at the start of the editable (collapsed range).\n        const rangeIsCollapsed = startContainer === endContainer && startOffset === endOffset;\n        const closestHandledElement = closestElement(endContainer, handledElemSelector);\n        if (!rangeIsCollapsed && closestHandledElement?.tagName !== \"BLOCKQUOTE\") {\n            return;\n        }\n        // Check if cursor is inside an empty heading, blockquote or pre.\n        if (!closestHandledElement || closestHandledElement.textContent.length) {\n            return;\n        }\n        // Check if unremovable.\n        if (this.getResource(\"unremovable_node_predicates\").some((p) => p(closestHandledElement))) {\n            return;\n        }\n        const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n        baseContainer.append(...closestHandledElement.childNodes);\n        closestHandledElement.after(baseContainer);\n        closestHandledElement.remove();\n        this.dependencies.selection.setCursorStart(baseContainer);\n        return true;\n    }\n\n    updateFontSelectorParams() {\n        this.font.displayName = this.fontName;\n    }\n\n    updateFontSizeSelectorParams() {\n        this.fontSize.displayName = this.fontSizeName;\n    }\n\n    processContentForClipboard(clonedContents, selection) {\n        const commonAncestorElement = closestElement(selection.commonAncestorContainer);\n        if (commonAncestorElement && !isBlock(clonedContents.firstChild)) {\n            // Get the list of ancestor elements starting from the provided\n            // commonAncestorElement up to the block-level element.\n            const blockEl = closestBlock(commonAncestorElement);\n            const ancestorsList = [\n                commonAncestorElement,\n                ...ancestors(commonAncestorElement, blockEl),\n            ];\n            // Wrap rangeContent with clones of their ancestors to keep the styles.\n            for (const ancestor of ancestorsList) {\n                // Keep the formatting by keeping inline ancestors and paragraph\n                // related ones like headings etc.\n                if (!isBlock(ancestor) || isParagraphRelatedElement(ancestor)) {\n                    const clone = ancestor.cloneNode();\n                    clone.append(...childNodes(clonedContents));\n                    clonedContents.appendChild(clone);\n                }\n            }\n        }\n        return clonedContents;\n    }\n\n    handleInsertWithinPre(insertContainer, block) {\n        if (block.nodeName !== \"PRE\") {\n            return insertContainer;\n        }\n        for (const cb of this.getResource(\"before_insert_within_pre_processors\")) {\n            insertContainer = cb(insertContainer);\n        }\n        const isDeepestBlock = (node) =>\n            isBlock(node) && ![...node.querySelectorAll(\"*\")].some(isBlock);\n        let linebreak;\n        const processNode = (node) => {\n            const children = childNodes(node);\n            if (isDeepestBlock(node) && node.nextSibling) {\n                linebreak = this.document.createTextNode(\"\\n\");\n                node.append(linebreak);\n            }\n            if (node.nodeType === Node.ELEMENT_NODE) {\n                unwrapContents(node);\n            }\n            for (const child of children) {\n                processNode(child);\n            }\n        };\n        for (const node of childNodes(insertContainer)) {\n            processNode(node);\n        }\n        return insertContainer;\n    }\n}\n", "import { Component, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nexport class FontSelector extends Component {\n    static template = \"html_editor.FontSelector\";\n    static props = {\n        ...toolbarButtonProps,\n        getItems: Function,\n        getDisplay: Function,\n        onSelected: Function,\n    };\n    static components = { Dropdown, DropdownItem };\n\n    setup() {\n        this.items = this.props.getItems();\n        this.state = useState(this.props.getDisplay());\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n    }\n\n    onSelected(item) {\n        this.props.onSelected(item);\n    }\n}\n", "import { Component, onMounted, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useDropdownState } from \"@web/core/dropdown/dropdown_hooks\";\nimport { useDebounced } from \"@web/core/utils/timing\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { getCSSVariableValue, getHtmlStyle } from \"@html_editor/utils/formatting\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nconst MAX_FONT_SIZE = 144;\n\nexport class FontSizeSelector extends Component {\n    static template = \"html_editor.FontSizeSelector\";\n    static props = {\n        getItems: Function,\n        getDisplay: Function,\n        onFontSizeInput: Function,\n        onSelected: Function,\n        onBlur: { type: Function, optional: true },\n        document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },\n        ...toolbarButtonProps,\n    };\n    static components = { Dropdown, DropdownItem };\n\n    setup() {\n        this.items = this.props.getItems();\n        this.state = useState(this.props.getDisplay());\n        this.dropdown = useDropdownState();\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n        this.iframeContentRef = useRef(\"iframeContent\");\n        this.debouncedCustomFontSizeInput = useDebounced(this.onCustomFontSizeInput, 200);\n\n        onMounted(() => {\n            const iframeEl = this.iframeContentRef.el;\n\n            const initFontSizeInput = () => {\n                const iframeDoc = iframeEl.contentWindow.document;\n\n                // Skip if already initialized.\n                if (this.fontSizeInput || !iframeDoc.body) {\n                    return;\n                }\n\n                this.fontSizeInput = iframeDoc.createElement(\"input\");\n                const isDarkMode = cookie.get(\"color_scheme\") === \"dark\";\n                const htmlStyle = getHtmlStyle(document);\n                const backgroundColor = getCSSVariableValue(\n                    isDarkMode ? \"gray-200\" : \"white\",\n                    htmlStyle\n                );\n                const color = getCSSVariableValue(\"black\", htmlStyle);\n                const fontFamily = getCSSVariableValue(\"o-system-fonts\", htmlStyle);\n                Object.assign(iframeDoc.body.style, {\n                    padding: \"0\",\n                    margin: \"0\",\n                });\n                Object.assign(this.fontSizeInput.style, {\n                    width: \"100%\",\n                    height: \"100%\",\n                    border: \"none\",\n                    outline: \"none\",\n                    textAlign: \"center\",\n                    backgroundColor: backgroundColor,\n                    color: color,\n                    fontFamily: fontFamily,\n                });\n                this.fontSizeInput.type = \"text\";\n                this.fontSizeInput.name = \"font-size-input\";\n                this.fontSizeInput.autocomplete = \"off\";\n                this.fontSizeInput.value = this.state.displayName;\n                iframeDoc.body.appendChild(this.fontSizeInput);\n                this.fontSizeInput.addEventListener(\"click\", () => {\n                    if (!this.dropdown.isOpen) {\n                        this.dropdown.open();\n                    }\n                });\n                this.fontSizeInput.addEventListener(\"input\", this.debouncedCustomFontSizeInput);\n                this.fontSizeInput.addEventListener(\n                    \"keydown\",\n                    this.onKeyDownFontSizeInput.bind(this)\n                );\n            };\n            if (iframeEl.contentDocument.readyState === \"complete\") {\n                initFontSizeInput();\n            } else {\n                // in firefox, iframe is not immediately available. we need to wait\n                // for it to be ready before mounting.\n                iframeEl.addEventListener(\n                    \"load\",\n                    () => {\n                        initFontSizeInput();\n                    },\n                    { once: true }\n                );\n            }\n        });\n        useEffect(\n            () => {\n                if (this.fontSizeInput) {\n                    // Update `fontSizeInputValue` whenever the font size changes.\n                    this.fontSizeInput.value = this.state.displayName;\n                }\n            },\n            () => [this.state.displayName]\n        );\n        useEffect(\n            () => {\n                if (this.fontSizeInput) {\n                    // Focus input on dropdown open, blur on close.\n                    if (this.dropdown.isOpen) {\n                        this.fontSizeInput.select();\n                    } else if (\n                        this.iframeContentRef.el?.contains(this.props.document.activeElement)\n                    ) {\n                        this.fontSizeInput.blur();\n                        this.props.onBlur?.();\n                    }\n                }\n            },\n            () => [this.dropdown.isOpen]\n        );\n    }\n\n    onCustomFontSizeInput(ev) {\n        let fontSize = parseInt(ev.target.value, 10);\n        if (fontSize > 0) {\n            fontSize = Math.min(fontSize, MAX_FONT_SIZE);\n            if (this.state.displayName !== fontSize) {\n                this.props.onFontSizeInput(`${fontSize}px`);\n            } else {\n                // Reset input if state.displayName does not change.\n                this.fontSizeInput.value = this.state.displayName;\n            }\n        }\n        this.fontSizeInput.focus();\n    }\n\n    onKeyDownFontSizeInput(ev) {\n        if ([\"Enter\", \"Tab\"].includes(ev.key) && this.dropdown.isOpen) {\n            this.dropdown.close();\n        } else if ([\"ArrowUp\", \"ArrowDown\"].includes(ev.key)) {\n            const fontSizeSelectorMenu = document.querySelector(\".o_font_size_selector_menu div\");\n            if (!fontSizeSelectorMenu) {\n                return;\n            }\n            ev.target.blur();\n            const fontSizeMenuItemToFocus =\n                ev.key === \"ArrowUp\"\n                    ? fontSizeSelectorMenu.lastElementChild\n                    : fontSizeSelectorMenu.firstElementChild;\n            if (fontSizeMenuItemToFocus) {\n                fontSizeMenuItemToFocus.focus();\n            }\n        }\n    }\n\n    onSelected(item) {\n        this.props.onSelected(item);\n    }\n}\n", "import { Component, onWillUpdateProps, useState, useRef } from \"@odoo/owl\";\nimport { CustomColorPicker as ColorPicker } from \"@web/core/color_picker/custom_color_picker/custom_color_picker\";\nimport {\n    isColorGradient,\n    standardizeGradient,\n    rgbaToHex,\n    convertCSSColorToRgba,\n} from \"@web/core/utils/colors\";\n\nexport class GradientPicker extends Component {\n    static components = { ColorPicker };\n    static template = \"html_editor.GradientPicker\";\n    static props = {\n        onGradientChange: { type: Function, optional: true },\n        onGradientPreview: { type: Function, optional: true },\n        setOnCloseCallback: { type: Function, optional: true },\n        setOperationCallbacks: { type: Function, optional: true },\n        selectedGradient: { type: String, optional: true },\n        noTransparency: { type: Boolean, optional: true },\n    };\n\n    setup() {\n        this.state = useState({\n            type: \"linear\",\n            angle: 135,\n            currentColorIndex: 0,\n            size: \"closest-side\",\n        });\n        this.positions = useState({ x: 25, y: 25 });\n        this.colors = useState([\n            { hex: \"#DF7CC4\", percentage: 0 },\n            { hex: \"#6C3582\", percentage: 100 },\n        ]);\n        this.cssGradients = useState({ preview: \"\", linear: \"\", radial: \"\", sliderThumbStyle: \"\" });\n        this.knobRef = useRef(\"gradientAngleKnob\");\n\n        if (this.props.selectedGradient && isColorGradient(this.props.selectedGradient)) {\n            // initialization of the gradient with the selected value\n            this.setGradientFromString(this.props.selectedGradient);\n        } else {\n            // initialization of the gradient with default value\n            this.onColorGradientChange();\n        }\n\n        onWillUpdateProps((newProps) => {\n            if (newProps.selectedGradient) {\n                this.setGradientFromString(newProps.selectedGradient);\n            }\n        });\n    }\n\n    setGradientFromString(gradient) {\n        if (!gradient || !isColorGradient(gradient)) {\n            return;\n        }\n        gradient = standardizeGradient(gradient);\n        const colors = [\n            ...gradient.matchAll(\n                /(#[0-9a-f]{6}|rgba?\\(\\s*[0-9]+\\s*,\\s*[0-9]+\\s*,\\s*[0-9]+\\s*[,\\s*[0-9.]*]?\\s*\\)|[a-z]+)\\s*([[0-9]+%]?)/g\n            ),\n        ].filter((color) => rgbaToHex(color[1]) !== \"#\");\n\n        this.colors.splice(0, this.colors.length);\n        for (const color of colors) {\n            this.colors.push({ hex: rgbaToHex(color[1]), percentage: color[2].replace(\"%\", \"\") });\n        }\n\n        const isLinear = gradient.startsWith(\"linear-gradient(\");\n        if (isLinear) {\n            const angle = gradient.match(/(-?[0-9]+)deg/);\n            if (angle) {\n                this.state.angle = parseInt(angle[1]);\n            }\n        } else {\n            this.state.type = \"radial\";\n            const sizeMatch = gradient.match(/(closest|farthest)-(side|corner)/);\n            const size = sizeMatch ? sizeMatch[0] : \"farthest-corner\";\n            this.state.size = size;\n\n            const position = gradient.match(/ at ([0-9]+)% ([0-9]+)%/) || [\"\", \"50\", \"50\"];\n            this.positions.x = position[1];\n            this.positions.y = position[2];\n        }\n\n        this.updateCssGradients();\n    }\n\n    selectType(type) {\n        this.state.type = type;\n        this.onColorGradientChange();\n    }\n\n    onAngleChange(ev) {\n        const angle = parseInt(ev.target.value);\n        if (!isNaN(angle)) {\n            const clampedAngle = Math.min(Math.max(angle, 0), 360);\n            ev.target.value = clampedAngle;\n            this.state.angle = clampedAngle;\n            this.onColorGradientChange();\n        }\n    }\n\n    onPositionChange(position, ev) {\n        const inputValue = parseFloat(ev.target.value);\n        if (!isNaN(inputValue)) {\n            const clampedValue = Math.min(Math.max(inputValue, 0), 100);\n            ev.target.value = clampedValue;\n            this.positions[position] = clampedValue;\n            this.onColorGradientChange();\n        }\n    }\n\n    onColorChange(color) {\n        const hex = rgbaToHex(color.cssColor);\n        this.colors[this.state.currentColorIndex].hex = hex;\n        this.onColorGradientChange();\n    }\n\n    onColorPreview(color) {\n        const hex = rgbaToHex(color.cssColor);\n        this.colors[this.state.currentColorIndex].hex = hex;\n        this.onColorGradientPreview();\n    }\n\n    onSizeChange(size) {\n        this.state.size = size;\n        this.onColorGradientChange();\n    }\n\n    onColorPercentageChange(colorIndex, ev) {\n        this.state.currentColorIndex = colorIndex;\n        this.colors[colorIndex].percentage = ev.target.value;\n        this.sortColors();\n        this.onColorGradientChange();\n    }\n\n    onGradientPreviewClick(ev) {\n        const width = parseInt(window.getComputedStyle(ev.target).width, 10);\n        const percentage = Math.round((100 * ev.offsetX) / width);\n        this.addColorStop(percentage);\n    }\n\n    addColorStop(percentage) {\n        let color;\n\n        let previousColor = this.colors.findLast((color) => color.percentage <= percentage);\n        let nextColor = this.colors.find((color) => color.percentage > percentage);\n        if (!previousColor && nextColor) {\n            // Click position is before the first color\n            color = nextColor.hex;\n        } else if (!nextColor && previousColor) {\n            //  Click position is after the last color\n            color = previousColor.hex;\n        } else if (nextColor && previousColor) {\n            const previousRatio =\n                (nextColor.percentage - percentage) /\n                (nextColor.percentage - previousColor.percentage);\n            const nextRatio = 1 - previousRatio;\n\n            previousColor = convertCSSColorToRgba(previousColor.hex);\n            nextColor = convertCSSColorToRgba(nextColor.hex);\n\n            const red = Math.round(previousRatio * previousColor.red + nextRatio * nextColor.red);\n            const green = Math.round(\n                previousRatio * previousColor.green + nextRatio * nextColor.green\n            );\n            const blue = Math.round(\n                previousRatio * previousColor.blue + nextRatio * nextColor.blue\n            );\n            const opacity = Math.round(\n                previousRatio * previousColor.opacity + nextRatio * nextColor.opacity\n            );\n            color = `rgba(${red}, ${green}, ${blue}, ${opacity / 100})`;\n        }\n\n        this.colors.push({ hex: color, percentage });\n        this.sortColors();\n        this.state.currentColorIndex = this.colors.findIndex(\n            (color) => color.percentage === percentage\n        );\n        this.onColorGradientChange();\n    }\n\n    removeColor(colorIndex) {\n        if (this.colors.length <= 2) {\n            return;\n        }\n        this.colors.splice(colorIndex, 1);\n        this.state.currentColorIndex = 0;\n        this.onColorGradientChange();\n    }\n\n    sortColors() {\n        this.colors = this.colors.sort((a, b) => a.percentage - b.percentage);\n    }\n\n    updateCssGradients() {\n        const gradientColors = this.colors\n            .map((color) => `${color.hex} ${color.percentage}%`)\n            .join(\", \");\n        let sliderThumbStyle = \"\";\n        // color the slider thumb with the color of the gradient\n        for (let i = 0; i < this.colors.length; i++) {\n            const selector = `.gradient-colors div:nth-child(${i + 1}) input[type=\"range\"]`;\n            const style = `background-color: ${this.colors[i].hex};`;\n            sliderThumbStyle += `${selector}::-webkit-slider-thumb { ${style} }\\n`;\n            sliderThumbStyle += `${selector}::-moz-range-thumb { ${style} }\\n`;\n        }\n\n        this.cssGradients.preview = `linear-gradient(90deg, ${gradientColors})`;\n        this.cssGradients.linear = `linear-gradient(${this.state.angle}deg, ${gradientColors})`;\n        this.cssGradients.radial = `radial-gradient(circle ${this.state.size} at ${this.positions.x}% ${this.positions.y}%, ${gradientColors})`;\n        this.cssGradients.sliderThumbStyle = sliderThumbStyle;\n    }\n\n    onColorGradientChange() {\n        this.updateCssGradients();\n        this.props?.onGradientChange(this.cssGradients[this.state.type]);\n    }\n\n    onColorGradientPreview() {\n        this.updateCssGradients();\n        this.props.onGradientPreview?.({ gradient: this.cssGradients[this.state.type] });\n    }\n\n    get currentColorHex() {\n        return this.colors?.[this.state.currentColorIndex]?.hex || \"#000000\";\n    }\n\n    onKnobMouseDown(ev) {\n        const knobEl = this.knobRef.el;\n        if (!knobEl) {\n            return;\n        }\n        const knobRadius = knobEl.offsetWidth / 2;\n        const knobRect = knobEl.getBoundingClientRect();\n        const centerX = knobRect.left + knobRadius;\n        const centerY = knobRect.top + knobRadius;\n\n        const updateAngle = (ev) => {\n            // calculate the differences between the mouse position and the\n            // center of the knob\n            const distanceX = ev.clientX - centerX;\n            const distanceY = ev.clientY - centerY;\n\n            // calculate the angle between the center and the mouse position\n            const angle = Math.atan2(distanceY, distanceX) * (180 / Math.PI);\n            this.state.angle = Math.round((angle + 360) % 360);\n        };\n\n        updateAngle(ev);\n        this.onColorGradientChange();\n\n        const onKnobMouseMove = (ev) => {\n            updateAngle(ev);\n            this.onColorGradientChange();\n        };\n        const onKnobMouseUp = () => document.removeEventListener(\"mousemove\", onKnobMouseMove);\n\n        document.addEventListener(\"mousemove\", onKnobMouseMove);\n        document.addEventListener(\"mouseup\", onKnobMouseUp, { once: true });\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { isEditorTab, isEmptyBlock, isProtected } from \"@html_editor/utils/dom_info\";\nimport { removeClass } from \"@html_editor/utils/dom\";\nimport { descendants, selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { closestBlock } from \"../utils/blocks\";\n\n/**\n * @typedef {import(\"@html_editor/editor\").EditorContext} EditorContext\n * @typedef {import(\"@html_editor/core/selection_plugin\").SelectionData} SelectionData\n * @typedef {import(\"plugins\").CSSSelector} CSSSelector\n * @typedef {import(\"plugins\").TranslatedString} TranslatedString\n */\n\n/**\n * @typedef {((\n *   selectionData: SelectionData,\n *   editable: EditorContext[\"editable\"]\n * ) => HTMLElement[] | NodeList)[]} hint_targets_providers\n * @typedef {{ selector: CSSSelector; text: TranslatedString; }[]} hints\n */\n\nexport class HintPlugin extends Plugin {\n    static id = \"hint\";\n    static dependencies = [\"history\", \"selection\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        selectionchange_handlers: this.updateHints.bind(this),\n        external_history_step_handlers: () => {\n            this.clearHints();\n            this.updateHints();\n        },\n        normalize_handlers: this.normalize.bind(this),\n        clean_for_save_handlers: ({ root }) => this.clearHints(root),\n        content_updated_handlers: this.updateHints.bind(this),\n\n        hint_targets_providers: (selectionData, editable) => {\n            if (!selectionData.currentSelectionIsInEditable || !selectionData.documentSelection) {\n                return [];\n            }\n            const blockEl = closestBlock(selectionData.documentSelection.anchorNode);\n            if (this.dependencies.selection.isNodeEditable(blockEl)) {\n                return [blockEl];\n            } else {\n                return [];\n            }\n        },\n        system_classes: [\"o-we-hint\"],\n        system_attributes: [\"o-we-hint-text\"],\n    };\n\n    setup() {\n        this.updateHints(this.editable);\n    }\n\n    destroy() {\n        super.destroy();\n        this.clearHints();\n    }\n\n    normalize() {\n        this.clearHints();\n        this.updateHints();\n    }\n\n    /**\n     * @param {HTMLElement} [root]\n     */\n    updateHints() {\n        const selectionData = this.dependencies.selection.getSelectionData();\n        const editableSelection = selectionData.editableSelection;\n        this.clearHints();\n        if (editableSelection.isCollapsed) {\n            const hints = this.getResource(\"hints\");\n            for (const provideTargets of this.getResource(\"hint_targets_providers\")) {\n                for (const target of provideTargets(selectionData, this.editable)) {\n                    const nodeHint = hints.find((h) => target.matches(h.selector))?.text;\n                    if (\n                        target &&\n                        nodeHint &&\n                        isEmptyBlock(target) &&\n                        !isProtected(target) &&\n                        !descendants(target).some(isEditorTab)\n                    ) {\n                        this.makeHint(target, nodeHint);\n                    }\n                }\n            }\n        }\n    }\n\n    makeHint(el, text) {\n        this.dispatchTo(\"make_hint_handlers\", el);\n        el.setAttribute(\"o-we-hint-text\", text);\n        el.classList.add(\"o-we-hint\");\n    }\n\n    removeHint(el) {\n        el.removeAttribute(\"o-we-hint-text\");\n        removeClass(el, \"o-we-hint\");\n        this.getResource(\"system_style_properties\").forEach((n) => el.style.removeProperty(n));\n    }\n\n    clearHints(root = this.editable) {\n        for (const elem of selectElements(root, \".o-we-hint\")) {\n            this.removeHint(elem);\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { isBlock, closestBlock } from \"@html_editor/utils/blocks\";\nimport { splitTextNode, unwrapContents } from \"@html_editor/utils/dom\";\nimport { isElement, isTextNode, isZwnbsp } from \"@html_editor/utils/dom_info\";\nimport { closestElement, selectElements, findFurthest } from \"@html_editor/utils/dom_traversal\";\nimport { DIRECTIONS, nodeSize } from \"@html_editor/utils/position\";\n\n/** @typedef {((codeElement: HTMLElement) => void)[]} to_inline_code_processors */\n\nexport class InlineCodePlugin extends Plugin {\n    static id = \"inlineCode\";\n    static dependencies = [\"selection\", \"history\", \"input\", \"split\", \"feff\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        input_handlers: this.onInput.bind(this),\n        selectionchange_handlers: this.handleSelectionChange.bind(this),\n        feff_providers: (root, cursors) =>\n            [...selectElements(root, \".o_inline_code\")].flatMap((code) =>\n                this.dependencies.feff.surroundWithFeffs(code, cursors)\n            ),\n    };\n\n    setup() {\n        this.addDomListener(this.document, \"keydown\", this.onKeyDown.bind(this));\n    }\n\n    handleSelectionChange() {\n        if (this.historySavePointRestore) {\n            delete this.historySavePointRestore;\n        }\n    }\n\n    onKeyDown() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        if (\n            selection.isCollapsed ||\n            closestElement(selection.anchorNode, \"code\") ||\n            closestElement(selection.focusNode, \"code\")\n        ) {\n            return;\n        }\n        const targetBlocks = this.dependencies.selection.getTargetedBlocks();\n        const hasTextNode = this.dependencies.selection.getTargetedNodes().some(isTextNode);\n        if (targetBlocks.size === 1 && hasTextNode) {\n            this.historySavePointRestore = this.dependencies.history.makeSavePoint();\n        }\n    }\n\n    onInput(ev) {\n        const selection = this.dependencies.selection.getEditableSelection();\n        if (ev.data !== \"`\" || closestElement(selection.anchorNode, \"code\")) {\n            return;\n        }\n        if (this.historySavePointRestore) {\n            this.historySavePointRestore();\n            let { anchorNode, anchorOffset, focusNode, focusOffset, direction } =\n                this.dependencies.split.splitSelection();\n            const blockEl = closestBlock(anchorNode);\n            // Adjust if anchor/focus directly equals block element\n            const deepChild = (node, offset) => (node === blockEl ? node.childNodes[offset] : node);\n            anchorNode = deepChild(anchorNode, anchorOffset);\n            focusNode = deepChild(focusNode, focusOffset);\n            if (direction === DIRECTIONS.LEFT) {\n                // Swap anchorNode and focusNode\n                [anchorNode, anchorOffset, focusNode, focusOffset] = [\n                    focusNode,\n                    focusOffset,\n                    anchorNode,\n                    anchorOffset,\n                ];\n            }\n            const furthestAnchorElement = findFurthest(anchorNode, blockEl, (n) => !isBlock(n));\n            let start = this.dependencies.split.splitAroundUntil(anchorNode, furthestAnchorElement);\n            const furthestFocusElement = findFurthest(focusNode, blockEl, (n) => !isBlock(n));\n            const end = this.dependencies.split.splitAroundUntil(focusNode, furthestFocusElement);\n\n            let codeElement = this.document.createElement(\"code\");\n            codeElement.classList.add(\"o_inline_code\");\n            start.before(codeElement);\n            while (start) {\n                if (isElement(start)) {\n                    for (const code of selectElements(start, \"code\")) {\n                        start = unwrapContents(code)[0];\n                    }\n                }\n                const next = start.nextSibling;\n                if (start.nodeName === \"IMG\") {\n                    // Only create <code> if we still have nodes to process\n                    // after this one.\n                    if (start !== end && next) {\n                        codeElement = this.document.createElement(\"code\");\n                        codeElement.classList.add(\"o_inline_code\");\n                    }\n                } else {\n                    if (!codeElement.isConnected) {\n                        start.before(codeElement);\n                    }\n                    codeElement.appendChild(start);\n                }\n                if (start === end) {\n                    break;\n                }\n                start = next;\n            }\n            this.dispatchTo(\"to_inline_code_processors\", codeElement);\n            this.dependencies.selection.setSelection({\n                anchorNode: codeElement,\n                anchorOffset: nodeSize(codeElement),\n            });\n            this.dependencies.history.addStep();\n            delete this.historySavePointRestore;\n            return;\n        }\n\n        // We just inserted a backtick, check if there was another\n        // one in the text.\n        let textNode = selection.startContainer;\n        const wholeText = textNode.wholeText;\n        const textHasTwoTicks = /`.*`/.test(wholeText);\n        // We don't apply the code tag if there is no content between the two `\n        if (textHasTwoTicks && wholeText.replace(/`/g, \"\").length) {\n            let offset = selection.startOffset;\n            let sibling = textNode.previousSibling;\n            while (sibling && sibling.nodeType === Node.TEXT_NODE) {\n                if (!isZwnbsp(sibling)) {\n                    offset += sibling.textContent.length;\n                }\n                sibling.textContent += textNode.textContent;\n                textNode.remove();\n                textNode = sibling;\n                sibling = sibling.previousSibling;\n            }\n            sibling = textNode.nextSibling;\n            while (sibling && sibling.nodeType === Node.TEXT_NODE) {\n                if (!isZwnbsp(sibling)) {\n                    textNode.textContent += sibling.textContent;\n                }\n                sibling.remove();\n                sibling = sibling.nextSibling;\n            }\n            this.dependencies.selection.setSelection({\n                anchorNode: textNode,\n                anchorOffset: offset,\n            });\n            this.dependencies.history.addStep();\n            const insertedBacktickIndex = offset - 1;\n            const textBeforeInsertedBacktick = textNode.textContent.substring(\n                0,\n                insertedBacktickIndex\n            );\n            let startOffset, endOffset;\n            const isClosingForward = textBeforeInsertedBacktick.includes(\"`\");\n            if (isClosingForward) {\n                // There is a backtick before the new backtick.\n                startOffset = textBeforeInsertedBacktick.lastIndexOf(\"`\");\n                endOffset = insertedBacktickIndex;\n            } else {\n                // There is a backtick after the new backtick.\n                const textAfterInsertedBacktick = textNode.textContent.substring(offset);\n                startOffset = insertedBacktickIndex;\n                endOffset = offset + textAfterInsertedBacktick.indexOf(\"`\");\n            }\n            // Split around the backticks if needed so text starts\n            // and ends with a backtick.\n            if (endOffset && endOffset < textNode.textContent.length) {\n                splitTextNode(textNode, endOffset + 1, DIRECTIONS.LEFT);\n            }\n            if (startOffset) {\n                splitTextNode(textNode, startOffset);\n            }\n            // Remove ticks.\n            textNode.textContent = textNode.textContent.substring(\n                1,\n                textNode.textContent.length - 1\n            );\n            // Insert code element.\n            const codeElement = this.document.createElement(\"code\");\n            codeElement.classList.add(\"o_inline_code\");\n            textNode.before(codeElement);\n            codeElement.append(textNode);\n            if (!codeElement.textContent.length) {\n                this.dependencies.history.addStep();\n                this.dependencies.selection.setSelection({\n                    anchorNode: codeElement.firstChild,\n                    anchorOffset: 1,\n                });\n            } else if (isClosingForward) {\n                // Move selection out of code element.\n                this.dependencies.history.addStep();\n                this.dependencies.selection.setSelection({\n                    anchorNode: codeElement.nextSibling,\n                    anchorOffset: 1,\n                });\n            } else {\n                this.dependencies.history.addStep();\n                this.dependencies.selection.setSelection({\n                    anchorNode: codeElement.firstChild,\n                    anchorOffset: 0,\n                });\n            }\n        }\n    }\n}\n", "import { registry } from \"@web/core/registry\";\n\nconst commandCategoryRegistry = registry.category(\"command_categories\");\n// A shortcut conflict occurs when actions are bound to the same\n// shortcut as the command palette. To avoid this, those actions can be\n// added to the command palette itself within this high priority category\n// so that they appear first in the results.\ncommandCategoryRegistry.add(\"shortcut_conflict\", {}, { sequence: 5 });\n", "import { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { URL_REGEX, cleanZWChars } from \"./utils\";\nimport { isImageUrl } from \"@html_editor/utils/url\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { childNodeIndex } from \"@html_editor/utils/position\";\n\n/**\n * @typedef {((text: string, url: string) => void | true)[]} paste_url_overrides\n */\n\nexport class LinkPastePlugin extends Plugin {\n    static id = \"linkPaste\";\n    static dependencies = [\"link\", \"clipboard\", \"selection\", \"dom\", \"history\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        before_paste_handlers: this.selectFullySelectedLink.bind(this),\n        paste_text_overrides: this.handlePasteText.bind(this),\n    };\n\n    /**\n     * @param {EditorSelection} selection\n     * @param {string} text\n     */\n    handlePasteText(selection, text) {\n        let splitAroundUrl;\n        // todo: add placeholder plugin that prevent any other plugin\n        // Avoid transforming dynamic placeholder pattern to url.\n        if (!text.match(/\\${.*}/gi)) {\n            splitAroundUrl = text.split(URL_REGEX);\n            // Remove 'http(s)://' capturing group from the result (indexes\n            // 2, 5, 8, ...).\n            splitAroundUrl = splitAroundUrl.filter((_, index) => (index + 1) % 3);\n        }\n        if (\n            !splitAroundUrl ||\n            splitAroundUrl.length < 3 ||\n            closestElement(selection.anchorNode, \"pre\")\n        ) {\n            // Let the default paste handle the text.\n            return false;\n        }\n        if (splitAroundUrl.length === 3 && !splitAroundUrl[0] && !splitAroundUrl[2]) {\n            // Pasted content is a single URL.\n            this.handlePasteTextUrl(selection, text);\n        } else {\n            this.handlePasteTextMultiUrl(selection, splitAroundUrl);\n        }\n        return true;\n    }\n    /**\n     * @param {EditorSelection} selection\n     * @param {string} text\n     */\n    handlePasteTextUrl(selection, text) {\n        const selectionIsInsideALink = !!closestElement(selection.anchorNode, \"a\");\n        const url = /^https?:\\/\\//i.test(text) ? text : \"http://\" + text;\n        if (selectionIsInsideALink) {\n            this.handlePasteTextUrlInsideLink(text, url);\n            return;\n        }\n        if (this.delegateTo(\"paste_url_overrides\", text, url)) {\n            return;\n        }\n        this.dependencies.link.insertLink(url, text);\n    }\n    /**\n     * @param {string} text\n     * @param {string} url\n     */\n    handlePasteTextUrlInsideLink(text, url) {\n        // A url cannot be transformed inside an existing link.\n        // An image can be embedded inside an existing link, a video cannot.\n        if (isImageUrl(url)) {\n            const img = this.document.createElement(\"IMG\");\n            img.setAttribute(\"src\", url);\n            this.dependencies.dom.insert(img);\n        } else {\n            this.dependencies.dom.insert(text);\n        }\n    }\n    /**\n     * @param {EditorSelection} selection\n     * @param {string[]} splitAroundUrl\n     */\n    handlePasteTextMultiUrl(selection, splitAroundUrl) {\n        const selectionIsInsideALink = !!closestElement(selection.anchorNode, \"a\");\n        for (let i = 0; i < splitAroundUrl.length; i++) {\n            const url = /^https?:\\/\\//gi.test(splitAroundUrl[i])\n                ? splitAroundUrl[i]\n                : \"http://\" + splitAroundUrl[i];\n            // Even indexes will always be plain text, and odd indexes will always be URL.\n            // A url cannot be transformed inside an existing link.\n            if (i % 2 && !selectionIsInsideALink) {\n                this.dependencies.dom.insert(\n                    this.dependencies.link.createLink(url, splitAroundUrl[i])\n                );\n            } else if (splitAroundUrl[i] !== \"\") {\n                this.dependencies.clipboard.pasteText(splitAroundUrl[i]);\n            }\n        }\n    }\n\n    /**\n     * @param {EditorSelection} selection\n     */\n    selectFullySelectedLink(selection) {\n        const link = closestElement(selection.anchorNode, \"a\");\n        if (\n            link?.parentElement?.isContentEditable &&\n            cleanZWChars(selection.textContent()) === cleanZWChars(link.innerText) &&\n            !this.getResource(\"unremovable_node_predicates\").some((p) => p(link))\n        ) {\n            this.dependencies.selection.setSelection({\n                anchorNode: link.parentElement,\n                anchorOffset: childNodeIndex(link) + (selection.direction ? 0 : 1),\n                focusNode: link.parentElement,\n                focusOffset: childNodeIndex(link) + (selection.direction ? 1 : 0),\n            });\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { unwrapContents } from \"@html_editor/utils/dom\";\nimport { closestElement, descendants, selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { findInSelection, callbacksForCursorUpdate } from \"@html_editor/utils/selection\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { LinkPopover } from \"./link_popover\";\nimport { DIRECTIONS, leftPos, nodeSize, rightPos } from \"@html_editor/utils/position\";\nimport { EMAIL_REGEX, URL_REGEX, cleanZWChars, deduceURLfromText } from \"./utils\";\nimport { isElement, isVisible, isZwnbsp } from \"@html_editor/utils/dom_info\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { isBlock, closestBlock } from \"@html_editor/utils/blocks\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { isBrowserFirefox } from \"@web/core/browser/feature_detection\";\n\n/** @typedef {import(\"@odoo/owl\").Component} Component */\n/** @typedef {import(\"plugins\").CSSSelector} CSSSelector */\n/**\n * @typedef {import(\"@html_editor/core/selection_plugin\").EditorSelection} EditorSelection\n */\n\n/**\n * @param {EditorSelection} selection\n */\nfunction isLinkActive(selection) {\n    const linkElementAnchor = closestElement(selection.anchorNode, \"A\");\n    const linkElementFocus = closestElement(selection.focusNode, \"A\");\n    if (linkElementFocus && linkElementAnchor) {\n        return linkElementAnchor === linkElementFocus;\n    }\n    if (linkElementAnchor || linkElementFocus) {\n        return true;\n    }\n\n    return false;\n}\n\n/**\n * @param { HTMLAnchorElement } link\n * @param {number} offset\n * @returns {\"start\"|\"end\"|false}\n */\nfunction isPositionAtEdgeofLink(link, offset) {\n    const childNodes = [...link.childNodes];\n    if (!childNodes.length) {\n        return \"end\";\n    }\n    let firstVisibleIndex = childNodes.findIndex(isVisible);\n    firstVisibleIndex = firstVisibleIndex === -1 ? 0 : firstVisibleIndex;\n    if (offset <= firstVisibleIndex) {\n        return \"start\";\n    }\n    let lastVisibleIndex = childNodes.reverse().findIndex(isVisible);\n    lastVisibleIndex = lastVisibleIndex === -1 ? 0 : childNodes.length - lastVisibleIndex;\n    if (offset >= lastVisibleIndex) {\n        return \"end\";\n    }\n    return false;\n}\n\nasync function fetchExternalMetaData(url) {\n    // Get the external metadata\n    try {\n        return await rpc(\"/html_editor/link_preview_external\", {\n            preview_url: url,\n        });\n    } catch {\n        // when it's not possible to fetch the metadata we don't want to block the ui\n        return;\n    }\n}\n\nasync function fetchInternalMetaData(url) {\n    // Get the internal metadata\n    const keepLastPromise = new KeepLast();\n    const urlParsed = new URL(url);\n    // Enforce the current page's protocol to prevent mixed content issues.\n    if (urlParsed.protocol !== window.location.protocol) {\n        urlParsed.protocol = window.location.protocol;\n    }\n\n    const result = await keepLastPromise\n        .add(fetch(urlParsed))\n        .then((response) => response.text())\n        .then(async (content) => {\n            const html_parser = new window.DOMParser();\n            const doc = html_parser.parseFromString(content, \"text/html\");\n            const internalUrlMetaData = await rpc(\"/html_editor/link_preview_internal\", {\n                preview_url: urlParsed.href,\n            });\n\n            internalUrlMetaData[\"favicon\"] = doc.querySelector(\"link[rel~='icon']\");\n            internalUrlMetaData[\"ogTitle\"] = doc.querySelector(\"[property='og:title']\");\n            internalUrlMetaData[\"title\"] = doc.querySelector(\"title\");\n\n            return internalUrlMetaData;\n        })\n        .catch((error) => {\n            // HTTP error codes should not prevent to edit the links, so we\n            // only check for proper instances of Error.\n            if (error instanceof Error) {\n                return Promise.reject(error);\n            }\n        });\n    return result;\n}\n\nasync function fetchAttachmentMetaData(url, ormService) {\n    try {\n        const urlParsed = new URL(url, window.location.origin);\n        const attachementId = parseInt(urlParsed.pathname.split(\"/\").pop());\n        return (\n            await ormService.read(\"ir.attachment\", [attachementId], [\"name\", \"mimetype\", \"type\"])\n        )[0];\n    } catch {\n        return { name: url };\n    }\n}\n\n/**\n * @typedef { Object } LinkShared\n * @property { LinkPlugin['createLink'] } createLink\n * @property { LinkPlugin['getPathAsUrlCommand'] } getPathAsUrlCommand\n * @property { LinkPlugin['insertLink'] } insertLink\n */\n\n/**\n * @typedef {((link: HTMLLinkElement) => boolean)[]} is_link_editable_predicates\n * @typedef {((link: HTMLLinkElement) => boolean)[]} legit_empty_link_predicates\n * @typedef {(() => boolean)[]} link_compatible_selection_predicates\n * @typedef {CSSSelector[]} immutable_link_selectors\n * @typedef {{\n *      PopoverClass: Component;\n *      isAvailable: (linkEl: HTMLLinkElement) => boolean;\n *      getProps: (props) => props;\n *  }[]} link_popovers\n * @typedef {((linkEl: HTMLAnchorElement) => void)[]} create_link_handlers\n */\n\nexport class LinkPlugin extends Plugin {\n    static id = \"link\";\n    static dependencies = [\n        \"dom\",\n        \"history\",\n        \"input\",\n        \"selection\",\n        \"split\",\n        \"lineBreak\",\n        \"overlay\",\n        \"color\",\n        \"baseContainer\",\n        \"feff\",\n    ];\n    static defaultConfig = {\n        allowStripDomain: true,\n    };\n    // @phoenix @todo: do we want to have createLink and insertLink methods in link plugin?\n    static shared = [\"createLink\", \"insertLink\", \"getPathAsUrlCommand\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"openLinkTools\",\n                title: _t(\"Link\"),\n                description: _t(\"Add a link\"),\n                icon: \"fa-link\",\n                run: ({ link, type } = {}) => this.openLinkTools(link, type),\n                isAvailable: (selection) => {\n                    const linkEl = findInSelection(selection, \"a\");\n                    return linkEl\n                        ? this.getResource(\"link_popovers\").some((p) => p.isAvailable(linkEl))\n                        : isHtmlContentSupported(selection);\n                },\n            },\n            {\n                id: \"removeLinkFromSelection\",\n                title: _t(\"Remove Link\"),\n                description: _t(\"Remove Link\"),\n                icon: \"fa-unlink\",\n                isAvailable: (selection) => {\n                    if (!isHtmlContentSupported(selection)) {\n                        return false;\n                    }\n                    for (const node of this.dependencies.selection.getTargetedNodes()) {\n                        const linkEl = closestElement(node, \"a\");\n                        if (\n                            linkEl &&\n                            !this.isLinkImmutable(linkEl) &&\n                            linkEl.parentElement.isContentEditable\n                        ) {\n                            return true;\n                        }\n                    }\n                },\n                run: this.removeLinkFromSelection.bind(this),\n            },\n        ],\n\n        toolbar_groups: [\n            withSequence(40, { id: \"link\", namespaces: [\"compact\", \"expanded\"] }),\n            withSequence(30, { id: \"image_link\", namespaces: [\"image\"] }),\n        ],\n        toolbar_items: [\n            {\n                id: \"link\",\n                groupId: \"link\",\n                commandId: \"openLinkTools\",\n                isActive: isLinkActive,\n                isDisabled: () => !this.isLinkAllowedOnSelection(),\n            },\n            {\n                id: \"unlink\",\n                groupId: \"link\",\n                commandId: \"removeLinkFromSelection\",\n                isDisabled: () => this.removeLinkFromSelectionIsDisabled(),\n            },\n            {\n                id: \"link\",\n                groupId: \"image_link\",\n                commandId: \"openLinkTools\",\n                isActive: isLinkActive,\n                isDisabled: () => !this.isLinkAllowedOnSelection(),\n            },\n            {\n                id: \"unlink\",\n                groupId: \"image_link\",\n                commandId: \"removeLinkFromSelection\",\n                isDisabled: () => this.removeLinkFromSelectionIsDisabled(),\n            },\n        ],\n\n        powerbox_categories: withSequence(50, { id: \"navigation\", name: _t(\"Navigation\") }),\n        powerbox_items: [\n            {\n                categoryId: \"navigation\",\n                commandId: \"openLinkTools\",\n            },\n            {\n                title: _t(\"Button\"),\n                description: _t(\"Add a button\"),\n                categoryId: \"navigation\",\n                commandId: \"openLinkTools\",\n                commandParams: { type: \"primary\" },\n            },\n        ],\n\n        power_buttons: withSequence(10, {\n            commandId: \"openLinkTools\",\n            commandParams: { type: \"primary\" },\n            description: _t(\"Add a button\"),\n            icon: \"fa-square\",\n        }),\n\n        link_popovers: [\n            withSequence(50, {\n                //Default option\n                PopoverClass: LinkPopover,\n                isAvailable: (linkEl) => !linkEl || !this.isLinkImmutable(linkEl),\n                getProps: (props) => props,\n            }),\n        ],\n\n        immutable_link_selectors: [\n            '[data-bs-toggle=\"tab\"]',\n            '[data-bs-toggle=\"collapse\"]',\n            '[data-bs-toggle=\"dropdown\"]',\n            \".dropdown-item\",\n            \"[data-oe-model]\",\n            \":has(>[data-oe-model])\",\n            \".o_prevent_link_editor a\",\n        ],\n        legit_empty_link_predicates: (linkEl) => linkEl.hasAttribute(\"data-mimetype\"),\n\n        /** Handlers */\n        beforeinput_handlers: withSequence(5, this.onBeforeInput.bind(this)),\n        input_handlers: this.onInputDeleteNormalizeLink.bind(this),\n        before_delete_handlers: this.updateCurrentLinkSyncState.bind(this),\n        delete_handlers: this.onInputDeleteNormalizeLink.bind(this),\n        before_paste_handlers: this.updateCurrentLinkSyncState.bind(this),\n        after_paste_handlers: this.onPasteNormalizeLink.bind(this),\n        selectionchange_handlers: this.handleSelectionChange.bind(this),\n        clean_for_save_handlers: ({ root }) => this.removeEmptyLinks(root),\n        normalize_handlers: this.normalizeLink.bind(this),\n        after_insert_handlers: this.handleAfterInsert.bind(this),\n\n        /** Overrides */\n        split_element_block_overrides: this.handleSplitBlock.bind(this),\n        insert_line_break_element_overrides: this.handleInsertLineBreak.bind(this),\n        delete_image_overrides: this.deleteImageLink.bind(this),\n        double_click_overrides: this.doubleClickLinkOverrides.bind(this),\n        triple_click_overrides: this.tripleClickButtonOverrides.bind(this),\n\n        /** Processors */\n        to_inline_code_processors: (node) => {\n            this.removeEmptyLinks(node);\n            for (const btn of selectElements(node, \"a.btn\")) {\n                // Remove all attributes from the button link except \"href\"\n                [...btn.attributes].forEach(\n                    (attr) => attr.name !== \"href\" && btn.removeAttribute(attr.name)\n                );\n            }\n        },\n    };\n\n    setup() {\n        this.initializePopovers();\n        this.currentOverlay = this.getActivePopover().overlay;\n        this.addDomListener(this.editable, \"click\", (ev) => {\n            const linkEl = ev.target.closest(\"a\");\n            if (linkEl) {\n                if (ev.ctrlKey || ev.metaKey) {\n                    window.open(linkEl.href, \"_blank\");\n                }\n                ev.preventDefault();\n            }\n        });\n        this.addDomListener(this.editable, \"mousedown\", () => {\n            this._isNavigatingByMouse = true;\n        });\n        this.addDomListener(this.editable, \"keydown\", () => {\n            delete this._isNavigatingByMouse;\n        });\n        this.addDomListener(this.editable, \"auxclick\", (ev) => {\n            if (ev.button === 1) {\n                const link = closestElement(ev.target, \"a\");\n                if (link?.href) {\n                    window.open(link.href, \"_blank\");\n                    ev.preventDefault();\n                }\n            }\n        });\n        // link creation is added to the command service because of a shortcut conflict,\n        // as ctrl+k is used for invoking the command palette\n        this.unregisterLinkCommandCallback = this.services.command?.add(\n            \"Create link\",\n            () => {\n                this.dependencies.selection.focusEditable();\n                // To avoid a race condition between the events spawn by :\n                // 1. the `focus editable` and\n                // 2. the odoo `Shortcut bar` closure\n                // Which can affect the link overlay opening sequence if we keep it in sync.\n                // Therefore we need to wait for the next tick before triggering openLinkTools.\n                setTimeout(() => this.openLinkTools());\n            },\n            {\n                hotkey: \"control+k\",\n                category: \"shortcut_conflict\",\n                isAvailable: () => {\n                    const selectionData = this.dependencies.selection.getSelectionData();\n                    return (\n                        selectionData.documentSelectionIsInEditable &&\n                        isHtmlContentSupported(selectionData.editableSelection)\n                    );\n                },\n            }\n        );\n\n        this.getExternalMetaData = memoize(fetchExternalMetaData);\n        this.getInternalMetaData = memoize(fetchInternalMetaData);\n        this.getAttachmentMetadata = memoize((url) =>\n            fetchAttachmentMetaData(url, this.services.orm)\n        );\n        this.LinkPopoverState = { editing: false };\n        this.newlyInsertedLinks = new Set();\n    }\n\n    destroy() {\n        this.unregisterLinkCommandCallback?.();\n    }\n\n    // -------------------------------------------------------------------------\n    // Commands\n    // -------------------------------------------------------------------------\n\n    /**\n     * @param {string} url\n     * @param {string} label\n     *\n     * @return {HTMLElement} link\n     */\n    createLink(url, label = \"\") {\n        const link = this.document.createElement(\"a\");\n        if (url !== undefined) {\n            link.setAttribute(\"href\", url);\n        }\n        for (const [param, value] of Object.entries(this.config.defaultLinkAttributes || {})) {\n            link.setAttribute(param, `${value}`);\n        }\n        link.innerText = label;\n        this.dispatchTo(\"create_link_handlers\", link);\n        return link;\n    }\n\n    /**\n     * @param {string} url\n     * @param {string} label\n     */\n    insertLink(url, label) {\n        const selection = this.dependencies.selection.getEditableSelection();\n        let link = closestElement(selection.anchorNode, \"a\");\n        if (link) {\n            link.setAttribute(\"href\", url);\n            link.innerText = label;\n        } else {\n            link = this.createLink(url, label);\n            this.dependencies.dom.insert(link);\n        }\n        this.dependencies.history.addStep();\n        const linkParent = link.parentElement;\n        const linkOffset = Array.from(linkParent.childNodes).indexOf(link);\n        this.dependencies.selection.setSelection(\n            { anchorNode: linkParent, anchorOffset: linkOffset + 1 },\n            { normalize: false }\n        );\n    }\n\n    /**\n     * @param {string} text\n     * @param {string} url\n     */\n    getPathAsUrlCommand(text, url) {\n        const pasteAsURLCommand = {\n            title: _t(\"Paste as URL\"),\n            description: _t(\"Create an URL.\"),\n            icon: \"fa-link\",\n            run: () => {\n                this.dependencies.dom.insert(this.createLink(url, text));\n                this.dependencies.history.addStep();\n            },\n        };\n        return pasteAsURLCommand;\n    }\n\n    isLinkAllowedOnSelection() {\n        if (this.getResource(\"link_compatible_selection_predicates\").some((p) => p())) {\n            return true;\n        }\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const targetedBlocks = targetedNodes.filter(isBlock);\n        const linksInSelection = targetedNodes.filter((n) => n.tagName === \"A\");\n        return (\n            linksInSelection.length < 2 &&\n            // Prevent a link across sibling blocks:\n            targetedBlocks.every((node) =>\n                targetedNodes.every((other) => node.contains(other) || other.contains(node))\n            )\n        );\n    }\n\n    /**\n     * open the Link popover to edit links\n     *\n     * @param {HTMLElement} [linkElement]\n     */\n    openLinkTools(linkElement, type) {\n        this.currentOverlay.close();\n        this.LinkPopoverState.editing = false;\n        if (!this.isLinkAllowedOnSelection()) {\n            return this.services.notification.add(\n                _t(\"Unable to create a link on the current selection.\"),\n                { type: \"danger\" }\n            );\n        }\n        let selection = this.dependencies.selection.getEditableSelection();\n        let cursorsToRestore = this.dependencies.selection.preserveSelection();\n        const commonAncestor = closestElement(selection.commonAncestorContainer);\n        linkElement = linkElement || findInSelection(selection, \"a\");\n        this.type = type;\n        if (\n            linkElement &&\n            (!linkElement.contains(selection.anchorNode) ||\n                !linkElement.contains(selection.focusNode))\n        ) {\n            this.extendLinkToSelection(linkElement, selection);\n            linkElement = findInSelection(selection, \"a\");\n            this.dependencies.history.addStep();\n            cursorsToRestore = this.dependencies.selection.preserveSelection();\n        }\n        this.linkInDocument = linkElement;\n        if (!linkElement) {\n            // create a new link element\n            linkElement = this.createLink(undefined, selection.textContent());\n        }\n\n        const selectionTextContent = selection?.textContent();\n        const isImage = !!findInSelection(selection, \"img\");\n\n        const applyCallback = (\n            url,\n            label,\n            classes,\n            customStyle,\n            linkTarget,\n            attachmentId,\n            relValue\n        ) => {\n            if (this.linkInDocument) {\n                if (url) {\n                    this.linkInDocument.href = url;\n                } else {\n                    this.linkInDocument.removeAttribute(\"href\");\n                }\n                if (relValue) {\n                    this.linkInDocument.setAttribute(\"rel\", relValue);\n                } else {\n                    this.linkInDocument.removeAttribute(\"rel\");\n                }\n                if (linkTarget) {\n                    this.linkInDocument.setAttribute(\"target\", linkTarget);\n                } else {\n                    this.linkInDocument.removeAttribute(\"target\");\n                }\n                if (!isImage) {\n                    if (classes) {\n                        this.linkInDocument.className = classes;\n                    } else {\n                        this.linkInDocument.removeAttribute(\"class\");\n                    }\n                    if (customStyle) {\n                        this.linkInDocument.setAttribute(\"style\", customStyle);\n                    } else {\n                        this.linkInDocument.removeAttribute(\"style\");\n                    }\n                    if (\n                        this.linkInDocument.childElementCount == 0 &&\n                        cleanZWChars(this.linkInDocument.innerText) !== label\n                    ) {\n                        this.linkInDocument.innerText = label;\n                        cursorsToRestore = null;\n                    }\n                }\n            } else if (url) {\n                // prevent the link creation if the url field was empty\n\n                // create a new link with current selection as a content\n                if ((selectionTextContent && selectionTextContent === label) || isImage) {\n                    const link = this.createLink(url);\n                    if (relValue) {\n                        link.setAttribute(\"rel\", relValue);\n                    }\n                    const image = isImage && findInSelection(selection, \"img\");\n                    const figure =\n                        image?.parentElement?.matches(\"figure[contenteditable=false]\") &&\n                        image.parentElement;\n                    if (figure) {\n                        figure.before(link);\n                        link.append(figure);\n                        if (link.parentElement === this.editable) {\n                            const baseContainer =\n                                this.dependencies.baseContainer.createBaseContainer();\n                            link.before(baseContainer);\n                            baseContainer.append(link);\n                        }\n                    } else {\n                        const content = this.dependencies.selection.extractContent(selection);\n                        link.append(content);\n                        link.normalize();\n                        cursorsToRestore = null;\n                        selection = this.dependencies.selection.getEditableSelection();\n                        const anchorClosestElement = closestElement(selection.anchorNode);\n                        if (commonAncestor !== anchorClosestElement) {\n                            // We force the cursor after the anchorClosestElement\n                            // To be sure the link is inserted in the correct place in the dom.\n                            const [anchorNode, anchorOffset] = rightPos(anchorClosestElement);\n                            this.dependencies.selection.setSelection(\n                                { anchorNode, anchorOffset },\n                                { normalize: false }\n                            );\n                        }\n                        this.dependencies.dom.insert(link);\n                    }\n                    this.linkInDocument = link;\n                } else if (label) {\n                    const link = this.createLink(url, label);\n                    if (classes) {\n                        link.className = classes;\n                    }\n                    if (customStyle) {\n                        link.setAttribute(\"style\", customStyle);\n                    }\n                    if (linkTarget) {\n                        link.setAttribute(\"target\", linkTarget);\n                    }\n                    this.linkInDocument = link;\n                    cursorsToRestore = null;\n                    this.dependencies.dom.insert(link);\n                }\n            }\n            if (attachmentId) {\n                this.linkInDocument.dataset.attachmentId = attachmentId;\n            }\n        };\n\n        this.restoreSavePoint = this.dependencies.history.makeSavePoint();\n        const props = {\n            document: this.document,\n            linkElement,\n            isImage: isImage,\n            onApply: (...args) => {\n                delete this._isNavigatingByMouse;\n                applyCallback(...args);\n                this.closeLinkTools(cursorsToRestore);\n                this.dependencies.selection.focusEditable();\n                this.dependencies.history.addStep();\n            },\n            onChange: applyCallback,\n            onDiscard: () => {\n                this.restoreSavePoint();\n                if (linkElement.isConnected) {\n                    this.openLinkTools(linkElement);\n                } else {\n                    this.linkInDocument = null;\n                    this.currentOverlay.close();\n                }\n                this.dependencies.selection.focusEditable();\n            },\n            onRemove: () => {\n                this.removeLinkInDocument();\n                this.linkInDocument = null;\n                this.currentOverlay.close();\n            },\n            onCopy: () => {\n                this.linkInDocument = null;\n                this.currentOverlay.close();\n            },\n            onEdit: () => {\n                this.restoreSavePoint = this.dependencies.history.makeSavePoint();\n            },\n            getInternalMetaData: this.getInternalMetaData,\n            getExternalMetaData: this.getExternalMetaData,\n            getAttachmentMetadata: this.getAttachmentMetadata,\n            recordInfo: this.config.getRecordInfo?.() || {},\n            canEdit:\n                !this.linkInDocument || !this.linkInDocument.classList.contains(\"o_link_readonly\"),\n            canRemove:\n                this.linkInDocument &&\n                this.linkInDocument.parentElement.isContentEditable &&\n                !this.isUnremovable(this.linkInDocument),\n            canUpload: this.config.allowFile,\n            onUpload: this.config.onAttachmentChange,\n            type: this.type || \"\",\n            LinkPopoverState: this.LinkPopoverState,\n            showReplaceTitleBanner: this.newlyInsertedLinks.has(linkElement),\n            allowCustomStyle: this.config.allowCustomStyle,\n            allowTargetBlank: this.config.allowTargetBlank,\n            allowStripDomain: this.config.allowStripDomain,\n        };\n\n        const popover = this.getActivePopover(linkElement);\n        if (popover) {\n            this.currentOverlay = popover.overlay;\n            if (!linkElement.href) {\n                this.LinkPopoverState.editing = true;\n            }\n            this.currentOverlay.open({ props: popover.getProps(props) });\n            if (this.linkInDocument) {\n                if (this.newlyInsertedLinks.has(this.linkInDocument)) {\n                    this.newlyInsertedLinks.delete(this.linkInDocument);\n                }\n            }\n        }\n    }\n\n    /**\n     * close the link tool\n     *\n     */\n    closeLinkTools(cursors = null) {\n        const link = this.linkInDocument;\n        this.linkInDocument = null;\n        // Some unit tests fail when this.overlay.isOpen but the DOM don't contain the linkPopover yet.\n        // Because of some kind of race condition between the hoot mock event and the owl renderer.\n        // This is why we check for the popover in the DOM.\n        if (this.currentOverlay.isOpen && document.querySelector(\".o-we-linkpopover\")) {\n            this.currentOverlay.close();\n            if (link && link.isConnected) {\n                this.dependencies.selection.setSelection({\n                    anchorNode: link,\n                    anchorOffset: 0,\n                    focusNode: link,\n                    focusOffset: nodeSize(link),\n                });\n                const saveCustomStyle = link.getAttribute(\"style\");\n                link.removeAttribute(\"style\");\n                this.dependencies.color.removeAllColor();\n                if (\n                    saveCustomStyle &&\n                    this.config.allowCustomStyle &&\n                    link.className.includes(\"custom\")\n                ) {\n                    link.setAttribute(\"style\", saveCustomStyle);\n                }\n                // Remove the current link (linkInDocument) if it has no content\n                if (cleanZWChars(link.textContent) === \"\" && !link.querySelector(\"img\")) {\n                    const [anchorNode, anchorOffset] = rightPos(link);\n                    // We force the cursor after the link before removing the link\n                    // to ensure we don't lose the selection position.\n                    this.dependencies.selection.setSelection(\n                        { anchorNode, anchorOffset },\n                        { normalize: false }\n                    );\n                    link.remove();\n                } else if (cursors) {\n                    cursors.restore();\n                } else {\n                    this.dependencies.selection.setCursorEnd(link);\n                }\n            }\n        }\n    }\n\n    normalizeLink(root) {\n        for (const anchorEl of selectElements(root, \"a\")) {\n            if (/btn(-[a-z0-9_-]*)custom/.test(anchorEl.className)) {\n                // if the link is a customized button, we don't want to change the color\n                continue;\n            }\n            const { color } = anchorEl.style;\n            const childNodes = [...anchorEl.childNodes];\n            // For each anchor element, if it has an inline color style,\n            // (converted from an external style), remove it from the anchor,\n            // create a font tag inside it, and move the color to the font tag.\n            // This ensures the color is applied to the font element instead of\n            // the anchor element itself.\n            if (color && childNodes.every((n) => !isBlock(n))) {\n                anchorEl.style.removeProperty(\"color\");\n                const font = selectElements(anchorEl, \"font\").next().value;\n                if (font && cleanZWChars(anchorEl.textContent) === font.textContent) {\n                    continue;\n                }\n                const newFont = this.document.createElement(\"font\");\n                newFont.append(...childNodes);\n                anchorEl.appendChild(newFont);\n                this.dependencies.color.colorElement(newFont, color, \"color\");\n            }\n\n            // When a link contains unsupported element (like an iframe or a link),\n            // we remove the link. Cases can happen when a image link is replaced\n            // by a document or a video\n            const hasUnsupportedMedia = anchorEl.querySelector(\"a, iframe\");\n            if (hasUnsupportedMedia) {\n                this.removeLinkInDocument(anchorEl);\n            }\n        }\n    }\n\n    handleSelectionChange(selectionData) {\n        const selection = selectionData.editableSelection;\n        if (\n            this._isNavigatingByMouse &&\n            selection.isCollapsed &&\n            selectionData.documentSelectionIsInEditable\n        ) {\n            delete this._isNavigatingByMouse;\n            const { startContainer, startOffset, endContainer, endOffset } = selection;\n            const linkElement = closestElement(startContainer, \"a\");\n            if (\n                linkElement &&\n                linkElement.textContent.startsWith(\"\\uFEFF\") &&\n                linkElement.textContent.endsWith(\"\\uFEFF\")\n            ) {\n                const linkDescendants = descendants(linkElement);\n\n                // Check if the cursor is positioned at the begining of link.\n                const isCursorAtStartOfLink = isZwnbsp(startContainer)\n                    ? linkDescendants.indexOf(startContainer) === 0\n                    : startContainer.nodeType === Node.TEXT_NODE &&\n                      linkDescendants.indexOf(startContainer) === 1 &&\n                      startOffset === 0;\n\n                // Check if the cursor is positioned at the end of link.\n                const isCursorAtEndOfLink = isZwnbsp(endContainer)\n                    ? linkDescendants.indexOf(endContainer) === linkDescendants.length - 1\n                    : endContainer.nodeType === Node.TEXT_NODE &&\n                      linkDescendants.indexOf(endContainer) === linkDescendants.length - 2 &&\n                      endOffset === nodeSize(endContainer);\n\n                // Handle selection movement.\n                if (isCursorAtStartOfLink || isCursorAtEndOfLink) {\n                    const [targetNode, targetOffset] = isCursorAtStartOfLink\n                        ? leftPos(linkElement)\n                        : rightPos(linkElement);\n                    this.dependencies.selection.setSelection({\n                        anchorNode: targetNode,\n                        anchorOffset: isCursorAtStartOfLink ? targetOffset - 1 : targetOffset + 1,\n                    });\n                    return;\n                }\n            }\n        }\n        if (!selectionData.currentSelectionIsInEditable) {\n            const popoverEl = document.querySelector(\".o-we-linkpopover\");\n            const anchorNode = document.getSelection()?.anchorNode;\n            if (\n                (popoverEl && !selectionData.documentSelection) ||\n                (anchorNode && isElement(anchorNode) && anchorNode.closest(\".o-we-linkpopover\"))\n            ) {\n                return;\n            }\n            this.linkInDocument = null;\n            this.closeLinkTools();\n        } else if (!selection.isCollapsed) {\n            // Open the link tool only if we have an image selected and the selection\n            // is fully contained in the image parent link.\n            const imageNode = findInSelection(selection, \"img\");\n            const parentElement = imageNode?.parentElement;\n            const linkContainingImage = imageNode && closestElement(imageNode, \"a\");\n            if (\n                linkContainingImage &&\n                this.isLinkAllowedOnSelection() &&\n                parentElement.contains(selection.anchorNode) &&\n                parentElement.contains(selection.focusNode)\n            ) {\n                this.openLinkTools(linkContainingImage);\n            } else {\n                this.linkInDocument = null;\n                this.closeLinkTools();\n            }\n        } else {\n            const closestLinkElement = closestElement(selection.anchorNode, \"A\");\n            const isLinkEditable = this.getResource(\"is_link_editable_predicates\").some((p) =>\n                p(closestLinkElement)\n            );\n            if (closestLinkElement && closestLinkElement.isContentEditable) {\n                if (closestLinkElement !== this.linkInDocument || !this.currentOverlay.isOpen) {\n                    this.openLinkTools(closestLinkElement);\n                }\n            } else if (isLinkEditable) {\n                this.openLinkTools(closestLinkElement);\n            } else {\n                this.linkInDocument = null;\n                this.closeLinkTools();\n            }\n        }\n    }\n\n    /**\n     * extend the given link element to include all content from the given selection\n     *\n     * @param {HTMLLinkElement} linkElement\n     * @return {boolean}\n     */\n    extendLinkToSelection(linkElement) {\n        this.dependencies.split.splitSelection();\n        const selectedNodes = this.dependencies.selection.getTargetedNodes();\n        let before = linkElement.previousSibling;\n        while (before !== null && selectedNodes.includes(before)) {\n            linkElement.insertBefore(before, linkElement.firstChild);\n            before = linkElement.previousSibling;\n        }\n        let after = linkElement.nextSibling;\n        while (after && selectedNodes.includes(after)) {\n            linkElement.appendChild(after);\n            after = linkElement.nextSibling;\n        }\n        this.dependencies.selection.setCursorEnd(linkElement);\n    }\n\n    isUnremovable(linkEl) {\n        return this.getResource(\"unremovable_node_predicates\").some((p) => p(linkEl));\n    }\n\n    /**\n     * Remove the link from the collapsed selection\n     */\n    removeLinkInDocument(link = this.linkInDocument) {\n        if (!link.parentElement.isContentEditable || this.isUnremovable(link)) {\n            return;\n        }\n        const cursors = this.dependencies.selection.preserveSelection();\n        if (link && link.isContentEditable && link.parentElement.isContentEditable) {\n            cursors.update(callbacksForCursorUpdate.unwrap(link));\n            unwrapContents(link);\n        }\n        cursors.restore();\n        this.linkInDocument = null;\n        this.dependencies.selection.focusEditable();\n        this.dependencies.history.addStep();\n    }\n\n    removeLinkFromSelectionIsDisabled(selection) {\n        for (const node of this.dependencies.selection.getTargetedNodes()) {\n            const linkEl = closestElement(node, \"a\");\n            if (linkEl && !this.isLinkImmutable(linkEl) && !this.isUnremovable(linkEl)) {\n                return false;\n            }\n        }\n        return true;\n    }\n    removeLinkFromSelection() {\n        const selection = this.dependencies.split.splitSelection();\n\n        // If not, unlink only the part(s) of the link(s) that are selected:\n        // `<a>a[b</a>c<a>d</a>e<a>f]g</a>` => `<a>a</a>[bcdef]<a>g</a>`.\n        let { anchorNode, focusNode, anchorOffset, focusOffset } = selection;\n        const direction = selection.direction;\n        // Split the links around the selection.\n        let [startLink, endLink] = [\n            closestElement(anchorNode, \"a\"),\n            closestElement(focusNode, \"a\"),\n        ];\n        let cursors;\n        if (startLink) {\n            // If a FEFF character is present as anchorNode or focusNode,\n            // restoring the selection later may throw an error. Therefore,\n            // FEFF characters should be cleaned before splitting the link.\n            cursors = this.dependencies.selection.preserveSelection();\n            this.dependencies.feff.removeFeffs(startLink, cursors);\n            cursors.restore();\n        }\n        if (endLink && startLink !== endLink) {\n            cursors = this.dependencies.selection.preserveSelection();\n            this.dependencies.feff.removeFeffs(endLink, cursors);\n            cursors.restore();\n        }\n        ({ anchorNode, focusNode, anchorOffset, focusOffset } =\n            this.dependencies.selection.getEditableSelection());\n        cursors = this.dependencies.selection.preserveSelection();\n        // to remove link from selected images\n        let targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const selectedImageNodes = targetedNodes.filter((node) => node.tagName === \"IMG\");\n        if (selectedImageNodes.length && startLink && endLink && startLink === endLink) {\n            for (const imageNode of selectedImageNodes) {\n                let imageLink;\n                const figure = closestElement(imageNode, \"figure\");\n                if (direction === DIRECTIONS.RIGHT) {\n                    imageLink = this.dependencies.split.splitAroundUntil(\n                        figure || imageNode,\n                        endLink\n                    );\n                } else {\n                    imageLink = this.dependencies.split.splitAroundUntil(\n                        figure || imageNode,\n                        startLink\n                    );\n                }\n                cursors.update(callbacksForCursorUpdate.unwrap(imageLink));\n                unwrapContents(imageLink);\n                if (figure && figure.parentElement !== this.editable) {\n                    // Remove the base container parent if there is one. Figure\n                    // is a block so it's not needed.\n                    unwrapContents(figure.parentElement);\n                }\n                // update the links at the selection\n                [startLink, endLink] = [\n                    closestElement(anchorNode, \"a\"),\n                    closestElement(focusNode, \"a\"),\n                ];\n            }\n            cursors.restore();\n            // when only unlink an inline image, add step after the unwrapping\n            if (\n                selectedImageNodes.length === 1 &&\n                selectedImageNodes.length === targetedNodes.length\n            ) {\n                this.dependencies.history.addStep();\n                return;\n            }\n        }\n        const startBlock = closestBlock(startLink);\n        const endBlock = closestBlock(endLink);\n        if (\n            startLink &&\n            startLink.isConnected &&\n            startLink.parentElement.isContentEditable &&\n            !this.isUnremovable(startLink)\n        ) {\n            anchorNode = this.dependencies.split.splitAroundUntil(anchorNode, startLink);\n            anchorOffset = direction === DIRECTIONS.RIGHT ? 0 : nodeSize(anchorNode);\n            this.dependencies.selection.setSelection(\n                { anchorNode, anchorOffset, focusNode, focusOffset },\n                { normalize: true }\n            );\n        }\n        // Only split the end link if it was not already done above.\n        if (\n            endLink &&\n            endLink.isConnected &&\n            endLink.parentElement.isContentEditable &&\n            !this.isUnremovable(endLink)\n        ) {\n            focusNode = this.dependencies.split.splitAroundUntil(focusNode, endLink);\n            focusOffset = direction === DIRECTIONS.RIGHT ? nodeSize(focusNode) : 0;\n            this.dependencies.selection.setSelection(\n                { anchorNode, anchorOffset, focusNode, focusOffset },\n                { normalize: true }\n            );\n        }\n        targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const links = new Set(\n            targetedNodes\n                .map((node) => closestElement(node, \"a\"))\n                .filter(\n                    (a) =>\n                        a &&\n                        a.isContentEditable &&\n                        a.parentElement.isContentEditable &&\n                        !this.isUnremovable(a)\n                )\n        );\n        if (links.size) {\n            for (const link of links) {\n                cursors.update(callbacksForCursorUpdate.unwrap(link));\n                unwrapContents(link);\n            }\n            cursors.restore();\n        }\n        if (startBlock) {\n            // Remove empty links splitted by `splitAroundUntil` due to\n            // adjacent invisible text nodes.\n            this.removeEmptyLinks(startBlock);\n        }\n        if (endBlock && endBlock !== startBlock) {\n            this.removeEmptyLinks(endBlock);\n        }\n        this.dependencies.history.addStep();\n    }\n\n    removeEmptyLinks(root) {\n        const remove = (node) => {\n            for (const child of node.childNodes) {\n                remove(child);\n            }\n            if (!this.isUnremovable(node)) {\n                node.before(...node.childNodes);\n                node.remove();\n            }\n        };\n        // @todo: preserve spaces\n        for (const link of root.querySelectorAll(\"a\")) {\n            if (\n                [...link.childNodes].some(isVisible) ||\n                !link.parentElement.isContentEditable ||\n                this.isUnremovable(link) ||\n                this.getResource(\"legit_empty_link_predicates\").some((p) => p(link))\n            ) {\n                continue;\n            }\n            remove(link);\n        }\n    }\n\n    updateCurrentLinkSyncState() {\n        const { anchorNode } = this.dependencies.selection.getEditableSelection();\n        const linkEl = closestElement(anchorNode, \"a\");\n        if (linkEl && linkEl.isContentEditable) {\n            const label = linkEl.innerText;\n            const url = deduceURLfromText(label, linkEl);\n            const href = linkEl.getAttribute(\"href\");\n            if (\n                url &&\n                (url === href || url + \"/\" === href || url === deduceURLfromText(href, linkEl))\n            ) {\n                this.isCurrentLinkInSync = true;\n            }\n        }\n    }\n\n    onBeforeInput(ev) {\n        if (ev.inputType === \"insertParagraph\" || ev.inputType === \"insertLineBreak\") {\n            const nodeForSelectionRestore = this.handleAutomaticLinkInsertion();\n            if (nodeForSelectionRestore) {\n                this.dependencies.selection.setCursorStart(nodeForSelectionRestore);\n                this.dependencies.history.addStep();\n            }\n        }\n        if (ev.inputType === \"insertText\" && ev.data === \" \") {\n            const nodeForSelectionRestore = this.handleAutomaticLinkInsertion();\n            if (nodeForSelectionRestore) {\n                // Since we manually insert a space here, we will be adding a history step\n                // after link creation with selection at the end of the link and another\n                // after inserting the space. So first undo will remove the space, and the\n                // second will undo the link creation.\n                this.dependencies.selection.setSelection({\n                    anchorNode: nodeForSelectionRestore,\n                    anchorOffset: 0,\n                });\n                this.dependencies.history.addStep();\n                nodeForSelectionRestore.textContent =\n                    \"\\u00A0\" + nodeForSelectionRestore.textContent;\n                this.dependencies.selection.setSelection({\n                    anchorNode: nodeForSelectionRestore,\n                    anchorOffset: 1,\n                });\n                this.dependencies.history.addStep();\n                ev.preventDefault();\n            }\n        }\n        this.updateCurrentLinkSyncState();\n    }\n\n    onInputDeleteNormalizeLink() {\n        const { anchorNode } = this.dependencies.selection.getEditableSelection();\n        const linkEl = closestElement(anchorNode, \"a\");\n        if (linkEl && linkEl.isContentEditable) {\n            const label = linkEl.innerText;\n            const url = deduceURLfromText(label, linkEl);\n            if (url && this.isCurrentLinkInSync) {\n                linkEl.setAttribute(\"href\", url);\n                this.isCurrentLinkInSync = false;\n                if (this.currentOverlay.isOpen) {\n                    this.currentOverlay.close();\n                }\n            }\n        }\n    }\n    onPasteNormalizeLink() {\n        this.updateCurrentLinkSyncState();\n        this.onInputDeleteNormalizeLink();\n    }\n\n    deleteImageLink(imageToDelete) {\n        if (\n            imageToDelete.parentElement.tagName === \"A\" &&\n            !this.isUnremovable(imageToDelete.parentElement) &&\n            imageToDelete.parentElement.parentElement.isContentEditable\n        ) {\n            // If the link is empty after removing the image, remove it.\n            const cursors = this.dependencies.selection.preserveSelection();\n            cursors.update(callbacksForCursorUpdate.remove(imageToDelete));\n            imageToDelete.remove();\n            this.closeLinkTools(cursors);\n            this.dependencies.history.addStep();\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Inserts a link in the editor. Called after pressing space or (shif +) enter.\n     * Performs a regex check to determine if the url has correct syntax.\n     */\n    handleAutomaticLinkInsertion() {\n        let selection = this.dependencies.selection.getEditableSelection();\n        if (\n            isHtmlContentSupported(selection) &&\n            !closestElement(selection.anchorNode, \"a\") &&\n            selection.anchorNode.nodeType === Node.TEXT_NODE\n        ) {\n            // Merge adjacent text nodes.\n            selection.anchorNode.parentNode.normalize();\n            selection = this.dependencies.selection.getEditableSelection();\n            const textSliced = selection.anchorNode.textContent.slice(0, selection.anchorOffset);\n            const textNodeSplitted = textSliced.split(/\\s/);\n            const potentialUrl = textNodeSplitted.pop();\n            // In case of multiple matches, only the last one will be converted.\n            const match = [...potentialUrl.matchAll(new RegExp(URL_REGEX, \"g\"))].pop();\n\n            if (match && !EMAIL_REGEX.test(match[0])) {\n                const nodeForSelectionRestore = selection.anchorNode.splitText(\n                    selection.anchorOffset\n                );\n                const url = match[2] ? match[0] : \"https://\" + match[0];\n                const startOffset = selection.anchorOffset - potentialUrl.length + match.index;\n                const text = selection.anchorNode.textContent.slice(\n                    startOffset,\n                    startOffset + match[0].length\n                );\n                const link = this.createLink(url, text);\n                // split the text node and replace the url text with the link\n                const textNodeToReplace = selection.anchorNode.splitText(startOffset);\n                textNodeToReplace.splitText(match[0].length);\n                selection.anchorNode.parentElement.replaceChild(link, textNodeToReplace);\n                if (link.getAttribute(\"href\") === link.textContent) {\n                    this.newlyInsertedLinks.add(link);\n                }\n                return nodeForSelectionRestore;\n            }\n        }\n    }\n\n    /**\n     * Special behavior for links: do not break the link at its edges, but\n     * rather before/after it.\n     *\n     * @param {Object} params\n     * @param {Element} params.targetNode\n     * @param {number} params.targetOffset\n     * @param {Element} params.blockToSplit\n     */\n    handleSplitBlock(params) {\n        return this.handleEnterAtEdgeOfLink(params, this.dependencies.split.splitElementBlock);\n    }\n\n    /**\n     * Special behavior for links: do not add a line break at its edges, but\n     * rather outside it.\n     *\n     * @param {Object} params\n     * @param {Element} params.targetNode\n     * @param {number} params.targetOffset\n     */\n    handleInsertLineBreak(params) {\n        return this.handleEnterAtEdgeOfLink(\n            params,\n            this.dependencies.lineBreak.insertLineBreakElement\n        );\n    }\n\n    /**\n     * @param {Object} params\n     * @param {Element} params.targetNode\n     * @param {number} params.targetOffset\n     * @param {Element} [params.blockToSplit]\n     * @param {Function} splitOrLineBreakCallback\n     */\n    handleEnterAtEdgeOfLink(params, splitOrLineBreakCallback) {\n        // @todo: handle target Node being a descendent of a link (iterate over\n        // leaves inside the link, rather than childNodes)\n        let { targetNode, targetOffset, blockToSplit } = params;\n        if (targetNode.tagName !== \"A\") {\n            return;\n        }\n        const edge = isPositionAtEdgeofLink(targetNode, targetOffset);\n        if (!edge) {\n            return;\n        }\n        [targetNode, targetOffset] = edge === \"start\" ? leftPos(targetNode) : rightPos(targetNode);\n        blockToSplit = targetNode;\n        splitOrLineBreakCallback({ ...params, targetNode, targetOffset, blockToSplit });\n        return true;\n    }\n\n    handleAfterInsert(insertedNodes) {\n        for (const node of insertedNodes) {\n            if (node.nodeType === Node.ELEMENT_NODE) {\n                for (const link of selectElements(node, \"A\")) {\n                    if (link.getAttribute(\"href\") === link.textContent && !this.isImage) {\n                        this.newlyInsertedLinks.add(link);\n                    }\n                }\n            }\n        }\n    }\n\n    initializePopovers() {\n        this.overlays = [];\n        this.getResource(\"link_popovers\").map((link_popover) => {\n            this.overlays.push({\n                overlay: this.dependencies.overlay.createOverlay(\n                    link_popover.PopoverClass,\n                    {\n                        closeOnPointerdown: true,\n                    },\n                    {\n                        sequence: 50,\n                    }\n                ),\n                isAvailable: link_popover.isAvailable,\n                getProps: link_popover.getProps,\n            });\n        });\n    }\n\n    getActivePopover(linkElement) {\n        return this.overlays.find((overlay) => overlay.isAvailable(linkElement));\n    }\n\n    isLinkImmutable(linkEl) {\n        return this.getResource(\"immutable_link_selectors\").some((s) => linkEl.matches(s));\n    }\n\n    doubleClickLinkOverrides(ev) {\n        const clickedLink = closestElement(ev.target, \"a\");\n        // If we double click on a link, limit the selection inside the link\n        if (clickedLink) {\n            // mimic the double click behavior of browsers\n            this.dependencies.selection.modifySelection(\"extend\", \"backward\", \"word\");\n            this.document.getSelection().collapseToStart();\n            this.dependencies.selection.modifySelection(\"extend\", \"forward\", \"word\");\n\n            const { anchorNode, focusNode, anchorOffset, focusOffset } =\n                this.dependencies.selection.getEditableSelection();\n\n            // We reset the word selection of double click to be inside the current clicked link\n            // when it spreads over different links. Because it's a word selection, we need to keep\n            // the correct offsets when resetting.\n            if (clickedLink.contains(anchorNode) && !clickedLink.contains(focusNode)) {\n                this.dependencies.selection.setSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode: clickedLink,\n                    focusOffset: nodeSize(clickedLink) - 1, // -1 to avoid the FEFF char\n                });\n            } else if (!clickedLink.contains(anchorNode) && clickedLink.contains(focusNode)) {\n                this.dependencies.selection.setSelection({\n                    anchorNode: clickedLink,\n                    anchorOffset: 1, // 1 to avoid the FEFF char\n                    focusNode,\n                    focusOffset,\n                });\n            } else if (!clickedLink.contains(anchorNode) && !clickedLink.contains(focusNode)) {\n                this.dependencies.selection.setSelection({\n                    anchorNode: clickedLink,\n                    anchorOffset: 1, // 1 to avoid the FEFF char\n                    focusNode: clickedLink,\n                    focusOffset: nodeSize(clickedLink) - 1, // -1 to avoid the FEFF char\n                });\n            } else {\n                this.dependencies.selection.setSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                });\n            }\n\n            return true;\n        }\n    }\n\n    tripleClickButtonOverrides(ev) {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const buttonElement = isBrowserFirefox()\n            ? findInSelection(selection, \"a.btn\")\n            : closestElement(selection.anchorNode, \"a.btn\");\n        if (buttonElement) {\n            this.dependencies.selection.setSelection({\n                anchorNode: buttonElement,\n                anchorOffset: 0,\n                focusNode: buttonElement,\n                focusOffset: nodeSize(buttonElement),\n            });\n            ev.preventDefault();\n            return true;\n        }\n    }\n}\n", "import { session } from \"@web/session\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Component, useState, useRef, useEffect, useExternalListener } from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { cleanZWChars, deduceURLfromText } from \"./utils\";\nimport { useColorPicker } from \"@web/core/color_picker/color_picker\";\nimport { CheckBox } from \"@web/core/checkbox/checkbox\";\n\nconst DEFAULT_CUSTOM_TEXT_COLOR = \"#714B67\";\nconst DEFAULT_CUSTOM_FILL_COLOR = \"#ffffff\";\n\nconst isCSSVariable = (color) => color.match(/^o-color-\\d$|^\\d{3}$/);\nconst formatColor = (color) => {\n    if (color.match(/^o-color-\\d$/gm)) {\n        return `var(--hb-cp-${color})`;\n    }\n    if (color.match(/^\\d{3}$/gm)) {\n        return `var(--${color})`;\n    }\n    return color;\n};\n\nexport class LinkPopover extends Component {\n    static template = \"html_editor.linkPopover\";\n    static props = {\n        document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },\n        linkElement: { validate: (el) => el.nodeType === Node.ELEMENT_NODE },\n        onApply: Function,\n        onChange: Function,\n        onDiscard: Function,\n        onRemove: Function,\n        onCopy: Function,\n        onEdit: Function,\n        getInternalMetaData: Function,\n        getExternalMetaData: Function,\n        getAttachmentMetadata: Function,\n        isImage: Boolean,\n        showReplaceTitleBanner: Boolean,\n        type: String,\n        LinkPopoverState: Object,\n        recordInfo: Object,\n        canEdit: { type: Boolean, optional: true },\n        canRemove: { type: Boolean, optional: true },\n        canUpload: { type: Boolean, optional: true },\n        onUpload: { type: Function, optional: true },\n        allowCustomStyle: { type: Boolean, optional: true },\n        allowTargetBlank: { type: Boolean, optional: true },\n        allowStripDomain: { type: Boolean, optional: true },\n        formatColor: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        canEdit: true,\n        canRemove: true,\n        formatColor: formatColor,\n    };\n    static components = { CheckBox };\n    colorsData = [\n        { type: \"\", label: _t(\"Link\"), btnPreview: \"link\" },\n        { type: \"primary\", label: _t(\"Button Primary\"), btnPreview: \"primary\" },\n        { type: \"secondary\", label: _t(\"Button Secondary\"), btnPreview: \"secondary\" },\n        { type: \"custom\", label: _t(\"Custom\"), btnPreview: \"custom\" },\n        // Note: by compatibility the dialog should be able to remove old\n        // colors that were suggested like the BS status colors or the\n        // alpha -> epsilon classes. This is currently done by removing\n        // all btn-* classes anyway.\n    ];\n    buttonSizesData = [\n        { size: \"sm\", label: _t(\"Small\") },\n        { size: \"\", label: _t(\"Medium\") },\n        { size: \"lg\", label: _t(\"Large\") },\n    ];\n    borderData = [\n        { style: \"solid\", label: \"\u2501\u2501\u2501\" },\n        { style: \"dashed\", label: \"\u254c\u254c\u254c\" },\n        { style: \"dotted\", label: \"\u2504\u2504\u2504\" },\n        { style: \"double\", label: \"\u2550\u2550\u2550\" },\n    ];\n    buttonShapeData = [\n        { shape: \"\", label: \"Default\" },\n        { shape: \"rounded-circle\", label: \"Default + Rounded\" },\n        { shape: \"outline\", label: \"Outline\" },\n        { shape: \"outline rounded-circle\", label: \"Outline + Rounded\" },\n        { shape: \"fill\", label: \"Fill\" },\n        { shape: \"fill rounded-circle\", label: \"Fill + Rounded\" },\n        { shape: \"flat\", label: \"Flat\" },\n    ];\n    setup() {\n        this.ui = useService(\"ui\");\n        this.notificationService = useService(\"notification\");\n        this.uploadService = useService(\"uploadLocalFiles\");\n\n        const linkElement = this.props.linkElement;\n        const textContent = cleanZWChars(linkElement.textContent);\n        const labelEqualsUrl =\n            textContent === linkElement.getAttribute(\"href\") ||\n            textContent + \"/\" === linkElement.getAttribute(\"href\");\n\n        const computedStyle = this.props.document.defaultView.getComputedStyle(linkElement);\n        const currentRelValues = linkElement.rel.split(\" \");\n        this.state = useState({\n            editing: this.props.LinkPopoverState.editing,\n            // `.getAttribute(\"href\")` instead of `.href` to keep relative url\n            url: linkElement.getAttribute(\"href\") || this.deduceUrl(textContent),\n            label: labelEqualsUrl ? \"\" : textContent,\n            previewIcon: {\n                /** @type {'fa'|'imgSrc'|'mimetype'} */\n                type: \"fa\",\n                value: \"fa-globe\",\n            },\n            urlTitle: \"\",\n            urlDescription: \"\",\n            linkPreviewName: \"\",\n            imgSrc: \"\",\n            type:\n                this.props.type ||\n                linkElement.className.match(/btn(-[a-z0-9_-]*)(primary|secondary|custom)/)?.pop() ||\n                \"\",\n            linkTarget: linkElement.target === \"_blank\" ? \"_blank\" : \"\",\n            directDownload: true,\n            isDocument: false,\n            buttonSize: linkElement.className.match(/btn-(sm|lg)/)?.[1] || \"\",\n            buttonShape: this.getButtonShape(),\n            customBorderSize: computedStyle.borderWidth.replace(\"px\", \"\") || \"0\",\n            customBorderStyle: computedStyle.borderStyle || \"solid\",\n            isImage: this.props.isImage,\n            showReplaceTitleBanner: this.props.showReplaceTitleBanner,\n            showLabel: !linkElement.childElementCount,\n            stripDomain: true,\n            showAdvancedOptions: false,\n            relAttributeOptions: {\n                nofollow: {\n                    label: \"nofollow\",\n                    description: _t(\"Tells search engines not to follow this link\"),\n                    isChecked: currentRelValues.includes(\"nofollow\"),\n                },\n                noreferrer: {\n                    label: \"noreferrer\",\n                    description: _t(\"Removes referrer information sent to the target site\"),\n                    isChecked: currentRelValues.includes(\"noreferrer\"),\n                },\n                sponsored: {\n                    label: \"sponsored\",\n                    description: _t(\"Indicates the link is sponsored or paid content\"),\n                    isChecked: currentRelValues.includes(\"sponsored\"),\n                },\n                noopener: {\n                    label: \"noopener\",\n                    description: _t(\n                        \"Prevents the new page from accessing the original window (security)\"\n                    ),\n                    isChecked: currentRelValues.includes(\"noopener\"),\n                },\n            },\n        });\n\n        const getTargetedElements = () => [this.props.linkElement];\n        this.customTextColorState = useState({\n            selectedColor: computedStyle.color || DEFAULT_CUSTOM_TEXT_COLOR,\n            defaultTab: \"solid\",\n            getTargetedElements,\n            mode: \"color\",\n        });\n        this.customTextResetPreviewColor = this.customTextColorState.selectedColor;\n        this.customFillColorState = useState({\n            selectedColor:\n                (computedStyle.backgroundImage === \"none\"\n                    ? undefined\n                    : computedStyle.backgroundImage) ||\n                computedStyle.backgroundColor ||\n                DEFAULT_CUSTOM_FILL_COLOR,\n            defaultTab: \"solid\",\n            getTargetedElements,\n            mode: \"background-color\",\n        });\n        this.customFillResetPreviewColor = this.customFillColorState.selectedColor;\n        this.customBorderColorState = useState({\n            selectedColor: computedStyle.borderColor || DEFAULT_CUSTOM_TEXT_COLOR,\n            defaultTab: \"solid\",\n            getTargetedElements,\n            mode: \"border-color\",\n        });\n        this.customBorderResetPreviewColor = this.customBorderColorState.selectedColor;\n\n        if (this.props.allowCustomStyle) {\n            const createCustomColorPicker = (refName, colorStateRef, resetValueRef) =>\n                useColorPicker(\n                    refName,\n                    {\n                        state: this[colorStateRef],\n                        enabledTabs:\n                            colorStateRef === \"customFillColorState\"\n                                ? [\"solid\", \"custom\", \"gradient\"]\n                                : [\"solid\", \"custom\"],\n                        getUsedCustomColors: () => [],\n                        colorPrefix: \"\",\n                        cssVarColorPrefix: \"hb-cp-\",\n                        applyColor: (colorValue) => {\n                            this[colorStateRef].selectedColor = colorValue;\n                            this[resetValueRef] = colorValue;\n                        },\n                        applyColorPreview: (colorValue) => {\n                            this[colorStateRef].selectedColor = colorValue;\n                            this.onChange();\n                        },\n                        applyColorResetPreview: () => {\n                            this[colorStateRef].selectedColor = this[resetValueRef];\n                            this.onChange();\n                        },\n                    },\n                    {\n                        env: this.__owl__.childEnv,\n                    }\n                );\n            this.customTextColorPicker = createCustomColorPicker(\n                \"customTextColorButton\",\n                \"customTextColorState\",\n                \"customTextResetPreviewColor\"\n            );\n            this.customFillColorPicker = createCustomColorPicker(\n                \"customFillColorButton\",\n                \"customFillColorState\",\n                \"customFillResetPreviewColor\"\n            );\n            this.customBorderColorPicker = createCustomColorPicker(\n                \"customBorderColorButton\",\n                \"customBorderColorState\",\n                \"customBorderResetPreviewColor\"\n            );\n        }\n        this.updateDocumentState();\n        this.editingWrapper = useRef(\"editing-wrapper\");\n        this.inputRef = useRef(\n            this.state.isImage || (this.state.label && !this.state.url) ? \"url\" : \"label\"\n        );\n        useEffect(\n            (el) => {\n                if (el) {\n                    el.focus();\n                }\n            },\n            () => [this.inputRef.el]\n        );\n        if (!this.state.editing) {\n            this.loadAsyncLinkPreview();\n        }\n        const onPointerDown = (ev) => {\n            if (this.state.isImage) {\n                return;\n            }\n            this.state.url ||= \"#\";\n            if (this.editingWrapper?.el && !this.editingWrapper.el.contains(ev.target)) {\n                this.onClickApply();\n            }\n        };\n        useExternalListener(this.props.document, \"pointerdown\", onPointerDown);\n        if (this.props.document !== document) {\n            // Listen to pointerdown outside the iframe\n            useExternalListener(document, \"pointerdown\", onPointerDown);\n        }\n    }\n\n    toggleAdvancedOptions() {\n        this.state.showAdvancedOptions = !this.state.showAdvancedOptions;\n    }\n\n    toggleRelAttr(attr) {\n        const option = this.state.relAttributeOptions[attr];\n        option.isChecked = !option.isChecked;\n    }\n\n    onChange() {\n        // Apply changes to update the link preview.\n        this.props.onChange(\n            this.state.url,\n            this.state.label,\n            this.classes,\n            this.customStyles,\n            this.state.linkTarget,\n            this.state.attachmentId\n        );\n        this.updateDocumentState();\n    }\n    onClickApply() {\n        const relOptions = this.state.relAttributeOptions;\n        const relValue = Object.keys(relOptions)\n            .filter((key) => relOptions[key].isChecked)\n            .join(\" \");\n        this.state.editing = false;\n        this.applyDeducedUrl();\n        this.props.onApply(\n            this.state.url,\n            this.state.label,\n            this.classes,\n            this.customStyles,\n            this.state.linkTarget,\n            this.state.attachmentId,\n            relValue\n        );\n    }\n    applyDeducedUrl() {\n        if (this.state.label === \"\") {\n            this.state.label = this.state.url;\n        }\n        const deducedUrl = this.deduceUrl(this.state.url);\n        this.state.url = deducedUrl\n            ? this.correctLink(deducedUrl)\n            : this.correctLink(this.state.url);\n        if (\n            this.props.allowStripDomain &&\n            this.state.stripDomain &&\n            this.isAbsoluteURLInCurrentDomain()\n        ) {\n            const urlObj = new URL(this.state.url, window.location.origin);\n            // Not necessarily equal to window.location.origin\n            // (see isAbsoluteURLInCurrentDomain)\n            this.state.url = this.state.url.replace(urlObj.origin, \"\");\n        }\n    }\n    onClickEdit() {\n        this.state.editing = true;\n        this.props.onEdit();\n        this.updateUrlAndLabel();\n    }\n    updateUrlAndLabel() {\n        this.state.url = this.props.linkElement.getAttribute(\"href\");\n\n        const textContent = cleanZWChars(this.props.linkElement.textContent);\n        const labelEqualsUrl =\n            textContent === this.props.linkElement.getAttribute(\"href\") ||\n            textContent + \"/\" === this.props.linkElement.getAttribute(\"href\");\n        this.state.label = labelEqualsUrl ? \"\" : textContent;\n    }\n    // TODO: remove in master\n    async onClickCopy(ev) {\n        ev.preventDefault();\n        await browser.navigator.clipboard.writeText(this.props.linkElement.href || \"\");\n        this.notificationService.add(_t(\"Link copied to clipboard.\"), {\n            type: \"success\",\n        });\n        this.props.onCopy();\n    }\n    onClickRemove() {\n        this.props.onRemove();\n    }\n\n    onKeydownEnter(ev) {\n        const isAutoCompleteDropdownOpen = document.querySelector(\".o-autocomplete--dropdown-menu\");\n        if (ev.key === \"Enter\" && !isAutoCompleteDropdownOpen && this.state.url) {\n            ev.preventDefault();\n            this.onClickApply();\n        }\n    }\n\n    onKeydown(ev) {\n        if (ev.key === \"Escape\") {\n            ev.preventDefault();\n            ev.stopImmediatePropagation();\n            this.onClickApply();\n        } else if (ev.key == \"Tab\") {\n            ev.preventDefault();\n            const focusableElements = [\n                ...this.editingWrapper.el.querySelectorAll(\"input, select, button:not([disabled])\"),\n            ];\n            const currentIndex = focusableElements.indexOf(document.activeElement);\n            const nextIndex =\n                (currentIndex + (ev.shiftKey ? -1 : 1) + focusableElements.length) %\n                focusableElements.length;\n            focusableElements[nextIndex].focus();\n        }\n    }\n\n    onInput() {\n        this.onChange();\n    }\n\n    onClickReplaceTitle() {\n        this.state.label = this.state.urlTitle;\n        this.onClickApply();\n    }\n\n    onClickDirectDownload(checked) {\n        this.state.directDownload = checked;\n        this.state.url = this.state.url.replace(\"&download=true\", \"\");\n        if (this.state.directDownload) {\n            this.state.url += \"&download=true\";\n        }\n    }\n\n    onClickNewWindow(checked) {\n        this.state.linkTarget = checked ? \"_blank\" : \"\";\n        if (!checked) {\n            this.state.relAttributeOptions.noopener.isChecked = false;\n        }\n    }\n\n    onClickStripDomain(checked) {\n        this.state.stripDomain = checked;\n    }\n\n    /**\n     * @private\n     */\n    async updateDocumentState() {\n        const url = this.state.url;\n        const urlObject = URL.parse(url, this.props.document.URL);\n        if (\n            url &&\n            (url.startsWith(\"/web/content/\") ||\n                (urlObject &&\n                    urlObject.pathname.startsWith(\"/web/content\") &&\n                    urlObject.host === document.location.host))\n        ) {\n            const { type } = await this.props.getAttachmentMetadata(url);\n            this.state.isDocument = type !== \"url\";\n            this.state.directDownload = url.includes(\"&download=true\");\n        } else {\n            this.state.isDocument = false;\n            this.state.directDownload = true;\n        }\n    }\n    correctLink(url) {\n        if (\n            url &&\n            !url.startsWith(\"tel:\") &&\n            !url.startsWith(\"mailto:\") &&\n            !url.includes(\"://\") &&\n            !url.startsWith(\"/\") &&\n            !url.startsWith(\"#\") &&\n            !url.startsWith(\"${\")\n        ) {\n            url = \"https://\" + url;\n        }\n        if (url && (url.startsWith(\"http:\") || url.startsWith(\"https:\"))) {\n            url = URL.parse(url) ? url : \"\";\n        }\n        return url;\n    }\n    deduceUrl(text) {\n        text = text.trim();\n        if (/^(https?:|mailto:|tel:)/.test(text)) {\n            // Text begins with a known protocol, accept it as valid URL.\n            return text;\n        } else {\n            return deduceURLfromText(text, this.props.linkElement) || \"\";\n        }\n    }\n    getButtonShape() {\n        const shapeToRegex = (shape) => {\n            const parts = shape.trim().split(/\\s+/);\n            const regexParts = parts.map((cls) => {\n                if ([\"outline\", \"fill\"].includes(cls)) {\n                    cls = `btn-${cls}`;\n                }\n                return `(?=.*\\\\b${cls}\\\\b)`;\n            });\n            return { regex: new RegExp(regexParts.join(\"\")), nbParts: parts.length };\n        };\n        // If multiple shapes match, prefer the one with more specificity.\n        let shapeMatched = \"\";\n        let matchScore = 0;\n        for (const { shape } of this.buttonShapeData) {\n            if (!shape) {\n                continue;\n            }\n            const { regex, nbParts } = shapeToRegex(shape);\n            if (regex.test(this.props.linkElement.className)) {\n                if (matchScore < nbParts) {\n                    matchScore = nbParts;\n                    shapeMatched = shape;\n                }\n            }\n        }\n        return shapeMatched;\n    }\n    /**\n     * link preview in the popover\n     */\n    resetPreview() {\n        this.state.previewIcon = { type: \"fa\", value: \"fa-globe\" };\n        this.state.urlTitle = this.state.url || _t(\"No URL specified\");\n        this.state.urlDescription = \"\";\n        this.state.linkPreviewName = \"\";\n    }\n    async loadAsyncLinkPreview() {\n        let url;\n        if (this.state.url === \"\") {\n            this.resetPreview();\n            this.state.previewIcon.value = \"fa-question-circle-o\";\n            return;\n        }\n        if (this.isLogoutUrl()) {\n            // The session ends if we fetch this url, so the preview is hardcoded\n            this.resetPreview();\n            this.state.urlTitle = _t(\"Logout\");\n            this.state.previewIcon.value = \"fa-sign-out\";\n            return;\n        }\n        if (this.isAttachmentUrl()) {\n            const { name, mimetype } = await this.props.getAttachmentMetadata(this.state.url);\n            this.resetPreview();\n            this.state.urlTitle = name;\n            this.state.previewIcon = { type: \"mimetype\", value: mimetype };\n            return;\n        }\n        try {\n            url = new URL(this.state.url, this.props.document.URL); // relative to absolute\n        } catch {\n            // Invalid URL, might happen with editor unsuported protocol. eg type\n            // `geo:37.786971,-122.399677`, become `http://geo:37.786971,-122.399677`\n            this.notificationService.add(_t(\"This URL is invalid. Preview couldn't be updated.\"), {\n                type: \"danger\",\n            });\n            return;\n        }\n        this.resetPreview();\n        const protocol = url.protocol;\n        if (!protocol.startsWith(\"http\")) {\n            const faMap = { \"mailto:\": \"fa-envelope-o\", \"tel:\": \"fa-phone\" };\n            const icon = faMap[protocol];\n            if (icon) {\n                this.state.previewIcon.value = icon;\n            }\n        } else if (\n            window.location.hostname !== url.hostname &&\n            !new RegExp(`^https?://${session.db}\\\\.odoo\\\\.com(/.*)?$`).test(url.origin)\n        ) {\n            // Preview pages from current website only. External website will\n            // most of the time raise a CORS error. To avoid that error, we\n            // would need to fetch the page through the server (s2s), involving\n            // enduser fetching problematic pages such as illicit content.\n            this.state.previewIcon = {\n                type: \"imgSrc\",\n                value: `https://www.google.com/s2/favicons?sz=16&domain=${encodeURIComponent(url)}`,\n            };\n\n            const externalMetadata = await this.props.getExternalMetaData(this.state.url);\n\n            this.state.urlTitle = externalMetadata?.og_title || this.state.url;\n            this.state.urlDescription = externalMetadata?.og_description || \"\";\n            this.state.imgSrc = externalMetadata?.og_image || \"\";\n            if (\n                externalMetadata?.og_image &&\n                this.state.label &&\n                this.state.urlTitle === this.state.url\n            ) {\n                this.state.urlTitle = this.state.label;\n            }\n        } else {\n            // Set state based on cached link meta data\n            // for record missing errors, we push a warning that the url is likely invalid\n            // for other errors, we log them to not block the ui\n            const internalMetadata = await this.props\n                .getInternalMetaData(url.href)\n                .catch((error) => {\n                    console.warn(`Error fetching internal metadata for ${url.href}:`, error);\n                    return {};\n                });\n            if (internalMetadata.favicon) {\n                this.state.previewIcon = {\n                    type: \"imgSrc\",\n                    value: internalMetadata.favicon.href,\n                };\n            }\n            if (internalMetadata.error_msg) {\n                this.notificationService.add(internalMetadata.error_msg, {\n                    type: \"warning\",\n                });\n            } else if (internalMetadata.other_error_msg) {\n                console.error(\n                    \"Internal meta data retrieve error for link preview: \" +\n                        internalMetadata.other_error_msg\n                );\n            } else {\n                this.state.linkPreviewName =\n                    internalMetadata.link_preview_name ||\n                    internalMetadata.display_name ||\n                    internalMetadata.name;\n                this.state.urlDescription = internalMetadata?.description || \"\";\n                this.state.urlTitle = this.state.linkPreviewName\n                    ? this.state.linkPreviewName\n                    : this.state.url;\n            }\n\n            if (\n                (internalMetadata.ogTitle || internalMetadata.title) &&\n                !this.state.linkPreviewName\n            ) {\n                this.state.urlTitle = internalMetadata.ogTitle\n                    ? internalMetadata.ogTitle.getAttribute(\"content\")\n                    : internalMetadata.title.text.trim();\n            }\n        }\n    }\n\n    get classes() {\n        const classes = [...this.props.linkElement.classList].filter(\n            (value) => !value.match(/^(btn.*|rounded-circle|flat|(text|bg)-(o-color-\\d$|\\d{3}$))$/)\n        );\n\n        let stylePrefix = \"\";\n        if (this.state.type) {\n            if (this.state.buttonSize) {\n                classes.push(`btn-${this.state.buttonSize}`);\n            }\n\n            if (this.state.buttonShape) {\n                const buttonShape = this.state.buttonShape.split(\" \");\n                if ([\"outline\", \"fill\"].includes(buttonShape[0])) {\n                    stylePrefix = `${buttonShape[0]}-`;\n                }\n                classes.push(buttonShape.slice(stylePrefix ? 1 : 0).join(\" \"));\n            }\n\n            classes.push(`btn`, `btn-${stylePrefix}${this.state.type}`);\n        }\n\n        const textColor = this.customTextColorState.selectedColor;\n        if (isCSSVariable(textColor)) {\n            classes.push(`text-${textColor}`);\n        }\n\n        const fillColor = this.customFillColorState.selectedColor;\n        if (isCSSVariable(fillColor)) {\n            classes.push(`bg-${fillColor}`);\n        }\n\n        // Ensure single space between classes\n        return classes.filter(Boolean).join(\" \");\n    }\n\n    get customStyles() {\n        if (!this.props.allowCustomStyle || this.state.type !== \"custom\") {\n            return false;\n        }\n        let customStyles = \"\";\n\n        const textColor = this.customTextColorState.selectedColor;\n        if (!isCSSVariable(textColor)) {\n            customStyles += `color: ${textColor}; `;\n        }\n\n        const fillColor = this.customFillColorState.selectedColor;\n        if (!isCSSVariable(fillColor)) {\n            const backgroundProperty = fillColor.includes(\"gradient\")\n                ? \"background-image\"\n                : \"background-color\";\n            customStyles += `${backgroundProperty}: ${fillColor}; `;\n        }\n\n        const borderColor = this.customBorderColorState.selectedColor;\n        customStyles += `border-width: ${this.state.customBorderSize}px; `;\n        customStyles += `border-color: ${formatColor(borderColor)}; `;\n        customStyles += `border-style: ${this.state.customBorderStyle}; `;\n\n        return customStyles;\n    }\n\n    async uploadFile() {\n        const { upload, getURL } = this.uploadService;\n        const { resModel, resId } = this.props.recordInfo;\n        const [attachment] = await upload({ resModel, resId, accessToken: true });\n        if (!attachment) {\n            // No file selected or upload failed\n            return;\n        }\n        this.props.onUpload?.(attachment);\n        this.state.url = getURL(attachment, { download: true, unique: true, accessToken: true });\n        this.state.label ||= attachment.name;\n        this.state.attachmentId = attachment.id;\n        this.onChange();\n    }\n\n    isLogoutUrl() {\n        return !!this.state.url.match(/\\/web\\/session\\/logout\\b/);\n    }\n    isAttachmentUrl() {\n        return !!this.state.url.match(/\\/web\\/content\\/\\d+/);\n    }\n    /**\n     * Checks if the given URL is using the domain where the content being\n     * edited is reachable, i.e. if this URL should be stripped of its domain\n     * part and converted to a relative URL if put as a link in the content.\n     *\n     * @private\n     * @returns {boolean}\n     */\n    isAbsoluteURLInCurrentDomain() {\n        // First check if it is a relative URL: if it is, we don't want to check\n        // further as we will always leave those untouched.\n        let hasProtocol;\n        try {\n            hasProtocol = !!new URL(this.state.url).protocol;\n        } catch {\n            hasProtocol = false;\n        }\n        if (!hasProtocol) {\n            return false;\n        }\n\n        const urlObj = new URL(this.state.url, window.location.origin);\n        // Chosen heuristic to detect someone trying to enter a link using\n        // its Odoo instance domain. We just suppose it should be a relative\n        // URL (if unexpected behavior, the user can just not enter its Odoo\n        // instance domain but its real domain, or opt-out from the domain\n        // stripping). Mentioning an .odoo.com domain, especially its own\n        // one, is always a bad practice anyway.\n        return (\n            urlObj.origin === window.location.origin ||\n            new RegExp(`^https?://${session.db}\\\\.odoo\\\\.com(/.*)?$`).test(urlObj.origin)\n        );\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { isBlock } from \"@html_editor/utils/blocks\";\n\nexport class OdooLinkSelectionPlugin extends Plugin {\n    static id = \"odooLinkSelection\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        ineligible_link_for_zwnbsp_predicates: [\n            (link) =>\n                [link, ...link.querySelectorAll(\"*\")].some(\n                    (el) => el.nodeName === \"IMG\" || isBlock(el)\n                ),\n            (link) => link.matches(\"a.nav-link\"),\n        ],\n        ineligible_link_for_selection_indication_predicates: (link) => link.matches(\".btn\"),\n    };\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestElement, selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { removeClass } from \"@html_editor/utils/dom\";\nimport { isProtected, isProtecting } from \"@html_editor/utils/dom_info\";\n\n/*\n    This plugin solves selection issues around links (allowing the cursor at the\n    inner and outer edges of links).\n\n    Every link receives 4 zero-width non-breaking spaces (unicode FEFF\n    characters, hereafter referred to as ZWNBSP):\n    - one before the link\n    - one as the link's first child\n    - one as the link's last child\n    - one after the link\n    like so: `//ZWNBSP//<a>//ZWNBSP//label//ZWNBSP//</a>//ZWNBSP`.\n\n    A visual indication ( `o_link_in_selection` class) is added to a link when\n    the selection is contained within it.\n\n    This is not applied in the following cases:\n\n    - in a navbar (since its links are managed via the snippets system, not\n    via pure edition) and, similarly, in .nav-link links\n    - in links that have content more complex than simple text\n    - on non-editable links or links that are not within the editable area\n */\n\n/**\n * @typedef { Object } LinkSelectionShared\n * @property { LinkSelectionPlugin['padLinkWithZwnbsp'] } padLinkWithZwnbsp\n */\n\n/**\n * @typedef {((link: HTMLLinkElement) => boolean)[]} ineligible_link_for_selection_indication_predicates\n * @typedef {((link: HTMLLinkElement) => boolean)[]} ineligible_link_for_zwnbsp_predicates\n */\n\nexport class LinkSelectionPlugin extends Plugin {\n    static id = \"linkSelection\";\n    static dependencies = [\"selection\", \"feff\"];\n    // TODO ABD: refactor to handle Knowledge comments inside this plugin without sharing padLinkWithZwnbsp.\n    static shared = [\"padLinkWithZwnbsp\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        selectionchange_handlers: this.resetLinkInSelection.bind(this),\n        clean_for_save_handlers: ({ root }) => this.clearLinkInSelectionClass(root),\n        normalize_handlers: () => this.resetLinkInSelection(),\n        feff_providers: this.addFeffsToLinks.bind(this),\n        system_classes: [\"o_link_in_selection\"],\n        selection_placeholder_container_predicates: (container) => {\n            if (container.nodeName === \"BUTTON\" || container.nodeName === \"A\") {\n                // We sometimes have buttons or links that are blocks with\n                // contenteditable=true but we never want to insert a paragraph\n                // in them.\n                // Note: this can be removed if `allowsParagraphRelatedElements`\n                // is adapted to return false in these cases.\n                return false;\n            }\n        },\n    };\n\n    addFeffsToLinks(root, cursors) {\n        return [...selectElements(root, \"a\")]\n            .filter(this.isLinkEligibleForZwnbsp.bind(this))\n            .flatMap((link) => this.dependencies.feff.surroundWithFeffs(link, cursors));\n    }\n\n    /**\n     * Take a link and pad it with non-break zero-width spaces to ensure that it\n     * is always possible to place the cursor at its inner and outer edges.\n     *\n     * @param {HTMLAnchorElement} link\n     */\n    padLinkWithZwnbsp(link) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        this.dependencies.feff.surroundWithFeffs(link, cursors);\n        cursors.restore();\n    }\n\n    isLinkEligibleForZwnbsp(link) {\n        return (\n            link.isContentEditable &&\n            link.parentElement.isContentEditable &&\n            this.editable.contains(link) &&\n            !isProtected(link) &&\n            !isProtecting(link) &&\n            !this.getResource(\"ineligible_link_for_zwnbsp_predicates\").some((p) => p(link))\n        );\n    }\n\n    isLinkEligibleForVisualIndication(link) {\n        return (\n            this.isLinkEligibleForZwnbsp(link) &&\n            !this.getResource(\"ineligible_link_for_selection_indication_predicates\").some(\n                (predicate) => predicate(link)\n            )\n        );\n    }\n\n    /**\n     * Apply the o_link_in_selection class if the selection is in a single link,\n     * remove it otherwise.\n     *\n     * @param {SelectionData} [selectionData]\n     */\n    resetLinkInSelection(selectionData = this.dependencies.selection.getSelectionData()) {\n        this.clearLinkInSelectionClass(this.editable);\n\n        const { anchorNode, focusNode } = selectionData.editableSelection;\n        const [anchorLink, focusLink] = [anchorNode, focusNode].map((node) =>\n            closestElement(node, \"a\")\n        );\n        const singleLinkInSelection = anchorLink === focusLink && anchorLink;\n\n        if (\n            singleLinkInSelection &&\n            this.isLinkEligibleForVisualIndication(singleLinkInSelection)\n        ) {\n            singleLinkInSelection.classList.add(\"o_link_in_selection\");\n        }\n    }\n\n    clearLinkInSelectionClass(root) {\n        for (const link of selectElements(root, \".o_link_in_selection\")) {\n            removeClass(link, \"o_link_in_selection\");\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\n\n/**\n * @typedef {import(\"@html_editor/core/user_command_plugin\").UserCommand} UserCommand\n *\n * @typedef {((url: string) => UserCommand)[]} paste_media_url_command_providers\n */\n\nexport class MediaUrlPastePlugin extends Plugin {\n    static id = \"mediaUrlPaste\";\n    static dependencies = [\"link\", \"dom\", \"history\", \"powerbox\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        paste_url_overrides: this.openPowerboxOnUrlPaste.bind(this),\n    };\n\n    /**\n     * @param {string} text\n     * @param {string} url\n     */\n    openPowerboxOnUrlPaste(text, url) {\n        const commands = this.getResource(\"paste_media_url_command_providers\")\n            .map((provider) => provider(url))\n            .filter(Boolean);\n        if (commands.length) {\n            commands.push(this.dependencies.link.getPathAsUrlCommand(text, url));\n            const restoreSavepoint = this.dependencies.history.makeSavePoint();\n            // Open powerbox with commands to embed media or paste as link.\n            // Insert URL as text, revert it later if a command is triggered.\n            this.dependencies.dom.insert(text);\n            this.dependencies.history.addStep();\n            this.dependencies.powerbox.openPowerbox({ commands, onApplyCommand: restoreSavepoint });\n            return true;\n        }\n    }\n}\n", "/* eslint-disable */\n\nconst tldWhitelist = [\n    \"com\", \"net\", \"org\", \"ac\", \"ad\", \"ae\", \"af\", \"ag\", \"ai\", \"al\", \"am\", \"an\",\n    \"ao\", \"aq\", \"ar\", \"as\", \"at\", \"au\", \"aw\", \"ax\", \"az\", \"ba\", \"bb\", \"bd\",\n    \"be\", \"bf\", \"bg\", \"bh\", \"bi\", \"bj\", \"bl\", \"bm\", \"bn\", \"bo\", \"br\", \"bq\",\n    \"bs\", \"bt\", \"bv\", \"bw\", \"by\", \"bz\", \"ca\", \"cc\", \"cd\", \"cf\", \"cg\", \"ch\",\n    \"ci\", \"ck\", \"cl\", \"cm\", \"cn\", \"co\", \"cr\", \"cs\", \"cu\", \"cv\", \"cw\", \"cx\",\n    \"cy\", \"cz\", \"dd\", \"de\", \"dj\", \"dk\", \"dm\", \"do\", \"dz\", \"ec\", \"ee\", \"eg\",\n    \"eh\", \"er\", \"es\", \"et\", \"eu\", \"fi\", \"fj\", \"fk\", \"fm\", \"fo\", \"fr\", \"ga\",\n    \"gb\", \"gd\", \"ge\", \"gf\", \"gg\", \"gh\", \"gi\", \"gl\", \"gm\", \"gn\", \"gp\", \"gq\",\n    \"gr\", \"gs\", \"gt\", \"gu\", \"gw\", \"gy\", \"hk\", \"hm\", \"hn\", \"hr\", \"ht\", \"hu\",\n    \"id\", \"ie\", \"il\", \"im\", \"in\", \"io\", \"iq\", \"ir\", \"is\", \"it\", \"je\", \"jm\",\n    \"jo\", \"jp\", \"ke\", \"kg\", \"kh\", \"ki\", \"km\", \"kn\", \"kp\", \"kr\", \"kw\", \"ky\",\n    \"kz\", \"la\", \"lb\", \"lc\", \"li\", \"lk\", \"lr\", \"ls\", \"lt\", \"lu\", \"lv\", \"ly\",\n    \"ma\", \"mc\", \"md\", \"me\", \"mf\", \"mg\", \"mh\", \"mk\", \"ml\", \"mm\", \"mn\", \"mo\",\n    \"mp\", \"mq\", \"mr\", \"ms\", \"mt\", \"mu\", \"mv\", \"mw\", \"mx\", \"my\", \"mz\", \"na\",\n    \"nc\", \"ne\", \"nf\", \"ng\", \"ni\", \"nl\", \"no\", \"np\", \"nr\", \"nu\", \"nz\", \"om\",\n    \"pa\", \"pe\", \"pf\", \"pg\", \"ph\", \"pk\", \"pl\", \"pm\", \"pn\", \"pr\", \"ps\", \"pt\",\n    \"pw\", \"py\", \"qa\", \"re\", \"ro\", \"rs\", \"ru\", \"rw\", \"sa\", \"sb\", \"sc\", \"sd\",\n    \"se\", \"sg\", \"sh\", \"si\", \"sj\", \"sk\", \"sl\", \"sm\", \"sn\", \"so\", \"sr\", \"ss\",\n    \"st\", \"su\", \"sv\", \"sx\", \"sy\", \"sz\", \"tc\", \"td\", \"tf\", \"tg\", \"th\", \"tj\",\n    \"tk\", \"tl\", \"tm\", \"tn\", \"to\", \"tp\", \"tr\", \"tt\", \"tv\", \"tw\", \"tz\", \"ua\",\n    \"ug\", \"uk\", \"um\", \"us\", \"uy\", \"uz\", \"va\", \"vc\", \"ve\", \"vg\", \"vi\", \"vn\",\n    \"vu\", \"wf\", \"ws\", \"ye\", \"yt\", \"yu\", \"za\", \"zm\", \"zr\", \"zw\", \"co\\\\.uk\"];\n\nconst urlRegexBase = `|(?:www.))[-a-zA-Z0-9@:%._\\\\+~#=]{2,256}\\\\.[a-zA-Z][a-zA-Z0-9]{1,62}|(?:[-a-zA-Z0-9@:%._\\\\+~#=]{2,256}\\\\.(?:${tldWhitelist.join(\n    \"|\"\n)})\\\\b))(?:(?:[/?#])[^\\\\s]*[^!.,})\\\\]'\"\\\\s]|(?:[^!(){}.,[\\\\]'\"\\\\s]+))?`;\nconst httpCapturedRegex = `(https?:\\\\/\\\\/)`;\n\nexport const URL_REGEX = new RegExp(`((?:(?:${httpCapturedRegex}${urlRegexBase})`, \"i\");\nexport const EMAIL_REGEX = /^(mailto:)?[\\w-.]+@(?:[\\w-]+\\.)+[\\w-]{2,4}$/i;\nexport const PHONE_REGEX = /^(tel:(?:\\/\\/)?)?\\+?[\\d\\s.\\-()/]{3,25}$/;\n\nexport function cleanZWChars(text) {\n    return text.replace(/\\u200B|\\uFEFF/g, \"\");\n}\n\n/**\n * Returns a complete URL if text is a valid email address, http URL or telephone\n * number, null otherwise.\n * The optional link parameter is used to prevent protocol switching between\n * 'http' and 'https'.\n *\n * @param {String} text\n * @param {HTMLAnchorElement} [link]\n * @returns {String|null}\n */\nexport function deduceURLfromText(text, link) {\n    const label = cleanZWChars(text).trim();\n    // Check first for e-mail.\n    let match = label.match(EMAIL_REGEX);\n    if (match) {\n        return match[1] ? match[0] : \"mailto:\" + match[0];\n    }\n    // Check for http link.\n    match = label.match(URL_REGEX);\n    if (match && match[0] === label) {\n        const currentHttpProtocol = (link?.href.match(/^http(s)?:\\/\\//gi) || [])[0];\n        if (match[2]) {\n            return match[0];\n        } else if (currentHttpProtocol) {\n            // Avoid converting a http link to https.\n            return currentHttpProtocol + match[0];\n        } else {\n            return \"https://\" + match[0];\n        }\n    }\n    // Check for telephone url.\n    match = label.match(PHONE_REGEX);\n    if (match) {\n        return (match[1] ? match[0] : \"tel:\" + match[0]).replace(/\\s+/g, \"\");\n    }\n    return null;\n}\n\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestBlock, isBlock } from \"@html_editor/utils/blocks\";\nimport {\n    removeClass,\n    removeStyle,\n    toggleClass,\n    unwrapContents,\n    wrapInlinesInBlocks,\n} from \"@html_editor/utils/dom\";\nimport {\n    getDeepestPosition,\n    isElement,\n    isEmptyBlock,\n    isListElement,\n    isListItemElement,\n    isParagraphRelatedElement,\n    isProtected,\n    isProtecting,\n    isShrunkBlock,\n    isVisibleTextNode,\n    listElementSelector,\n} from \"@html_editor/utils/dom_info\";\nimport {\n    closestElement,\n    descendants,\n    getAdjacents,\n    selectElements,\n    ancestors,\n    childNodes,\n    firstLeaf,\n    lastLeaf,\n} from \"@html_editor/utils/dom_traversal\";\nimport { childNodeIndex, nodeSize } from \"@html_editor/utils/position\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { compareListTypes, createList, insertListAfter, isListItem } from \"./utils\";\nimport { callbacksForCursorUpdate } from \"@html_editor/utils/selection\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { FONT_SIZE_CLASSES, getFontSizeOrClass, getHtmlStyle } from \"@html_editor/utils/formatting\";\nimport { getTextColorOrClass, TEXT_CLASSES_REGEX } from \"@html_editor/utils/color\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { ListSelector } from \"./list_selector\";\nimport { reactive } from \"@odoo/owl\";\nimport { composeToolbarButton } from \"../toolbar/toolbar\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { pick } from \"@web/core/utils/objects\";\nimport { weakMemoize } from \"@html_editor/utils/functions\";\nimport { isColorGradient } from \"@web/core/utils/colors\";\n\nconst listSelectorItems = [\n    {\n        id: \"bulleted_list\",\n        commandId: \"toggleListUL\",\n        mode: \"UL\",\n    },\n    {\n        id: \"numbered_list\",\n        commandId: \"toggleListOL\",\n        mode: \"OL\",\n    },\n    {\n        id: \"checklist\",\n        commandId: \"toggleListCL\",\n        mode: \"CL\",\n    },\n];\n\nexport class ListPlugin extends Plugin {\n    static id = \"list\";\n    static dependencies = [\n        \"baseContainer\",\n        \"tabulation\",\n        \"history\",\n        \"input\",\n        \"split\",\n        \"selection\",\n        \"delete\",\n        \"dom\",\n        \"color\",\n    ];\n    static defaultConfig = {\n        allowChecklist: true,\n    };\n    toolbarListSelectorKey = reactive({ value: 0 });\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"toggleListUL\",\n                title: _t(\"Bulleted list\"),\n                description: _t(\"Create a simple bulleted list\"),\n                icon: \"fa-list-ul\",\n                run: () => this.toggleListCommand({ mode: \"UL\" }),\n                isAvailable: this.canToggleList.bind(this),\n            },\n            {\n                id: \"toggleListOL\",\n                title: _t(\"Numbered list\"),\n                description: _t(\"Create a list with numbering\"),\n                icon: \"fa-list-ol\",\n                run: ({ listStyle } = {}) => this.toggleListCommand({ mode: \"OL\", listStyle }),\n                isAvailable: this.canToggleList.bind(this),\n            },\n            {\n                id: \"toggleListCL\",\n                title: _t(\"Checklist\"),\n                description: _t(\"Track tasks with a checklist\"),\n                icon: \"fa-check-square-o\",\n                run: () => this.toggleListCommand({ mode: \"CL\" }),\n                isAvailable: (selection) =>\n                    this.config.allowChecklist && this.canToggleList(selection),\n            },\n        ],\n        shortcuts: [\n            { hotkey: \"control+shift+7\", commandId: \"toggleListOL\" },\n            { hotkey: \"control+shift+8\", commandId: \"toggleListUL\" },\n            { hotkey: \"control+shift+9\", commandId: \"toggleListCL\" },\n        ],\n        shorthands: [\n            {\n                pattern: /^1[.)]$/,\n                commandId: \"toggleListOL\",\n            },\n            {\n                pattern: /^a[.)]$/,\n                commandId: \"toggleListOL\",\n                commandParams: { listStyle: \"lower-alpha\" },\n            },\n            {\n                pattern: /^A[.)]$/,\n                commandId: \"toggleListOL\",\n                commandParams: { listStyle: \"upper-alpha\" },\n            },\n            {\n                pattern: /^[-*]$/,\n                commandId: \"toggleListUL\",\n            },\n            {\n                pattern: /^\\[\\]$/,\n                commandId: \"toggleListCL\",\n            },\n        ],\n        toolbar_items: [\n            withSequence(5, {\n                id: \"list\",\n                groupId: \"layout\",\n                description: _t(\"Toggle List\"),\n                Component: ListSelector,\n                props: {\n                    getButtons: () => this.listSelectorButtons,\n                    getListMode: this.getListMode.bind(this),\n                    key: this.toolbarListSelectorKey,\n                },\n                isAvailable: this.canToggleList.bind(this),\n            }),\n        ],\n        powerbox_items: [\n            {\n                categoryId: \"structure\",\n                commandId: \"toggleListUL\",\n            },\n            {\n                categoryId: \"structure\",\n                commandId: \"toggleListOL\",\n            },\n            {\n                categoryId: \"structure\",\n                commandId: \"toggleListCL\",\n            },\n        ].map((item) => withSequence(5, item)),\n        power_buttons: [\n            { commandId: \"toggleListUL\" },\n            { commandId: \"toggleListOL\" },\n            { commandId: \"toggleListCL\" },\n        ].map((item) => withSequence(15, item)),\n\n        hints: [{ selector: `LI, LI > ${baseContainerGlobalSelector}`, text: _t(\"List\") }],\n\n        /** Handlers */\n        normalize_handlers: this.normalize.bind(this),\n        step_added_handlers: this.updateToolbarButtons.bind(this),\n        delete_handlers: this.adjustListPaddingOnDelete.bind(this),\n\n        /** Overrides */\n        delete_backward_overrides: this.handleDeleteBackward.bind(this),\n        delete_range_overrides: this.handleDeleteRange.bind(this),\n        tab_overrides: this.handleTab.bind(this),\n        shift_tab_overrides: this.handleShiftTab.bind(this),\n        split_element_block_overrides: this.handleSplitBlock.bind(this),\n        color_apply_overrides: this.applyColorToListItem.bind(this),\n        format_selection_handlers: this.applyFormatToListItem.bind(this),\n        node_to_insert_processors: this.processNodeToInsert.bind(this),\n        clipboard_content_processors: this.processContentForClipboard.bind(this),\n        before_insert_within_pre_processors: this.insertListWithinPre.bind(this),\n\n        fully_selected_node_predicates: (node, selection, range) => {\n            if (node.nodeName === \"LI\") {\n                const nonListChildren = childNodes(node).filter(\n                    (n) => ![\"UL\", \"OL\"].includes(n.nodeName)\n                );\n                if (!nonListChildren.length) {\n                    return;\n                }\n                const startLeaf = firstLeaf(nonListChildren[0]);\n                const endLeaf = lastLeaf(nonListChildren[nonListChildren.length - 1]);\n                return (\n                    range.isPointInRange(startLeaf, 0) &&\n                    range.isPointInRange(endLeaf, nodeSize(endLeaf))\n                );\n            }\n        },\n    };\n\n    setup() {\n        this.addDomListener(this.editable, \"touchstart\", this.onPointerdown);\n        this.addDomListener(this.editable, \"mousedown\", this.onPointerdown);\n        this.listSelectorButtons = this.getListSelectorButtons();\n        this.canToggleListMemoized = weakMemoize(\n            (selection) =>\n                isHtmlContentSupported(selection) && this.getBlocksToToggleList().length > 0\n        );\n    }\n\n    toggleListCommand({ mode, listStyle } = {}) {\n        this.toggleList(mode, listStyle);\n        this.dependencies.history.addStep();\n    }\n\n    getBlocksToToggleList() {\n        const targetedBlocks = [...this.dependencies.selection.getTargetedBlocks()];\n        return targetedBlocks.filter(\n            (block) =>\n                !descendants(block).some((descendant) => targetedBlocks.includes(descendant)) &&\n                block.isContentEditable &&\n                ![\"OL\", \"UL\"].includes(block.tagName)\n        );\n    }\n\n    canToggleList(selection) {\n        return this.canToggleListMemoized(selection);\n    }\n\n    // --------------------------------------------------------------------------\n    // Commands\n    // --------------------------------------------------------------------------\n\n    /**\n     * Classifies the selected blocks into three categories:\n     * - LI that are part of a list of the same mode as the target one.\n     * - Lists (UL or OL) that need to have its mode switched to the target mode.\n     * - Blocks that need to be converted to lists.\n     *\n     *  If (and only if) all blocks fall into the first category, the list items\n     *  are converted into paragraphs (result is toggle list OFF).\n     *  Otherwise, the LIs in this category remain unchanged and the other two\n     *  categories are processed.\n     *\n     * @param {string} mode - The list mode to toggle (UL, OL, CL).\n     * @param {string} [listStyle] - The list style ( see listStyle css property)\n     * @throws {Error} If an invalid list type is provided.\n     */\n    toggleList(mode, listStyle) {\n        if (![\"UL\", \"OL\", \"CL\"].includes(mode)) {\n            throw new Error(`Invalid list type: ${mode}`);\n        }\n        if (mode === \"CL\" && !!listStyle) {\n            throw new Error(`listStyle is not compatible with \"CL\" list type`);\n        }\n\n        // @todo @phoenix: original implementation removed whitespace-only text nodes from targetedNodes.\n        // Check if this is necessary.\n\n        // Classify targeted blocks.\n        const sameModeListItems = new Set();\n        const nonListBlocks = new Set();\n        const listsToSwitch = new Set();\n        for (const block of this.getBlocksToToggleList()) {\n            const li = closestElement(block, isListItem);\n            if (li) {\n                if (this.getListMode(li.parentElement) === mode) {\n                    sameModeListItems.add(li);\n                } else {\n                    listsToSwitch.add(li.parentElement);\n                }\n            } else {\n                nonListBlocks.add(block);\n            }\n        }\n\n        // Apply changes.\n        if (listsToSwitch.size || nonListBlocks.size) {\n            for (const list of listsToSwitch) {\n                const cursors = this.dependencies.selection.preserveSelection();\n                const newList = this.switchListMode(list, mode);\n                cursors.remapNode(list, newList).restore();\n            }\n            for (const block of nonListBlocks) {\n                const list = this.blockToList(block, mode, listStyle);\n                if (listStyle) {\n                    list.style.listStyle = listStyle;\n                }\n            }\n        } else {\n            for (const li of sameModeListItems) {\n                this.liToBlocks(li);\n            }\n        }\n    }\n\n    normalize(root = this.editable) {\n        const closestNestedLI = closestElement(root, \"li:has(ul, ol)\");\n        if (closestNestedLI && closestNestedLI.closest(\"ul, ol\")) {\n            root = closestNestedLI.parentElement;\n        }\n        for (let element of selectElements(root, \"ul, ol, li\")) {\n            if (isProtected(element) || isProtecting(element)) {\n                continue;\n            }\n            for (const fn of [\n                this.liWithoutParentToP,\n                this.mergeSimilarLists,\n                this.normalizeLI,\n                this.normalizeNestedList,\n            ]) {\n                const updatedElement = fn.call(this, element);\n                if (updatedElement) {\n                    element = updatedElement;\n                }\n            }\n        }\n    }\n\n    // --------------------------------------------------------------------------\n    // Helpers for toggleList\n    // --------------------------------------------------------------------------\n\n    /**\n     * @param {HTMLElement} element\n     * @param {\"UL\"|\"OL\"|\"CL\"} mode\n     */\n    blockToList(element, mode) {\n        if (element.matches(baseContainerGlobalSelector)) {\n            return this.baseContainerToList(element, mode);\n        }\n        // @todo @phoenix: check for callbacks registered as resources instead?\n        if (element.matches(\"td, th, li.nav-item\")) {\n            return this.blockContentsToList(element, mode);\n        }\n        let list;\n        const cursors = this.dependencies.selection.preserveSelection();\n        if (element === this.editable) {\n            // @todo @phoenix: check if this is needed\n            // Refactor insertListAfter in order to make proper preserveCursor\n            // possible.\n            const callingNode = element.firstChild;\n            const group = getAdjacents(callingNode, (n) => !isBlock(n));\n            list = insertListAfter(this.document, callingNode, mode, group);\n        } else {\n            const parent = element.parentNode;\n            const childIndex = childNodeIndex(element);\n            list = insertListAfter(this.document, element, mode, [element]);\n            cursors.update((cursor) => {\n                if (cursor.node === parent) {\n                    if (cursor.offset === childIndex) {\n                        [cursor.node, cursor.offset] = [list.firstChild, 0];\n                    } else if (cursor.offset === childIndex + 1) {\n                        [cursor.node, cursor.offset] = [list.firstChild, 1];\n                    }\n                }\n            });\n            if (element.hasAttribute(\"dir\")) {\n                list.setAttribute(\"dir\", element.getAttribute(\"dir\"));\n            }\n        }\n        cursors.restore();\n        return list;\n    }\n\n    /**\n     * @param {HTMLElement} baseContainer baseContainer Element (can be a div with the\n     *        necessary classes/attributes).\n     * @param {\"UL\"|\"OL\"|\"CL\"} mode\n     */\n    baseContainerToList(baseContainer, mode) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        const list = insertListAfter(this.document, baseContainer, mode, childNodes(baseContainer));\n        const textAlign = baseContainer.style.getPropertyValue(\"text-align\");\n        if (textAlign) {\n            // Copy text-align style from base container to li.\n            list.firstElementChild.style.setProperty(\"text-align\", textAlign);\n            baseContainer.style.removeProperty(\"text-align\");\n        }\n        this.dependencies.dom.copyAttributes(baseContainer, list);\n        this.adjustListPadding(list);\n        baseContainer.remove();\n        cursors.remapNode(baseContainer, list.firstChild).restore();\n        return list;\n    }\n\n    blockContentsToList(block, mode) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        const list = insertListAfter(this.document, block.lastChild, mode, [...block.childNodes]);\n        cursors.remapNode(block, list.firstChild).restore();\n        return list;\n    }\n\n    /**\n     * Converts a list element and its nested elements to the given list mode.\n     *\n     * @see switchListMode\n     * @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} node - HTML element\n     * representing a list or list item.\n     * @param {string} newMode - Target list mode\n     * @param {Object} options\n     * @returns {HTMLUListElement|HTMLOListElement|HTMLLIElement} node - Modified\n     * list element after conversion.\n     */\n    convertList(node, newMode) {\n        if (![\"UL\", \"OL\", \"LI\"].includes(node.tagName)) {\n            return;\n        }\n        const listMode = this.getListMode(node);\n        if (listMode && newMode !== listMode) {\n            node = this.switchListMode(node, newMode);\n        }\n        for (const child of node.children) {\n            this.convertList(child, newMode);\n        }\n        return node;\n    }\n\n    /**\n     * @param {HTMLElement} element\n     * @returns {\"UL\"|\"OL\"|\"CL\"|undefined}\n     */\n    getListMode(listContainerEl) {\n        if (![\"UL\", \"OL\"].includes(listContainerEl.tagName)) {\n            return;\n        }\n        if (listContainerEl.tagName === \"OL\") {\n            return \"OL\";\n        }\n        return listContainerEl.classList.contains(\"o_checklist\") ? \"CL\" : \"UL\";\n    }\n\n    /**\n     * Switches the list mode of the given list element.\n     *\n     * @param {HTMLOListElement|HTMLUListElement} list - The list element to switch the mode of.\n     * @param {\"UL\"|\"OL\"|\"CL\"} newMode - The new mode to switch to.\n     * @param {Object} options\n     * @returns {HTMLOListElement|HTMLUListElement} The modified list element.\n     */\n    switchListMode(list, newMode) {\n        if (this.getListMode(list) === newMode) {\n            return;\n        }\n        const newTag = newMode === \"CL\" ? \"UL\" : newMode;\n        const newList = this.dependencies.dom.setTagName(list, newTag);\n        // Remove any previously set list-style so that when changing the list\n        // type, the new list can show its correct default marker style.\n        newList.style.removeProperty(\"list-style\");\n        for (const li of newList.children) {\n            li.style.removeProperty(\"list-style\");\n        }\n        removeClass(newList, \"o_checklist\");\n        if (newMode === \"CL\") {\n            newList.classList.add(\"o_checklist\");\n        }\n        this.adjustListPadding(newList);\n        return newList;\n    }\n\n    /**\n     * Unwraps LI's content into blocks. Equivalent to fully outdenting the LI.\n     *\n     * @param {HTMLLIElement} li\n     */\n    liToBlocks(li) {\n        while (li) {\n            li = this.outdentLI(li);\n        }\n    }\n\n    // --------------------------------------------------------------------------\n    // Helpers for normalize\n    // --------------------------------------------------------------------------\n\n    liWithoutParentToP(element) {\n        const isOrphan = element.nodeName === \"LI\" && !element.closest(\"ul, ol\");\n        if (!isOrphan) {\n            return;\n        }\n        if (element.children.length && [...element.children].every(isBlock)) {\n            // Unwrap <li> if each of its children is a block element.\n            unwrapContents(element);\n        } else {\n            // Otherwise, wrap its content in a new <p> element.\n            const paragraph = this.dependencies.baseContainer.createBaseContainer();\n            element.replaceWith(paragraph);\n            paragraph.replaceChildren(...element.childNodes);\n        }\n    }\n\n    mergeSimilarLists(element) {\n        if (\n            !element.matches(\"ul, ol, li.oe-nested\") ||\n            (element.matches(\"li.oe-nested\") && !element.querySelector(\"ul, ol\"))\n        ) {\n            return;\n        }\n        const previousSibling = element.previousElementSibling;\n        if (\n            previousSibling &&\n            element.isContentEditable &&\n            previousSibling.isContentEditable &&\n            (compareListTypes(previousSibling, element) ||\n                (element.tagName === \"LI\" &&\n                    isListItem(previousSibling) &&\n                    isListElement(element.firstChild)))\n        ) {\n            const cursors = this.dependencies.selection.preserveSelection();\n            cursors.update(callbacksForCursorUpdate.merge(element));\n            previousSibling.append(...element.childNodes);\n            // @todo @phoenix: what if unremovable/unmergeable?\n            element.remove();\n            this.adjustListPadding(previousSibling);\n            cursors.restore();\n            return previousSibling;\n        }\n    }\n\n    /**\n     * Wraps inlines in P to avoid inlines with block siblings.\n     */\n    normalizeLI(element) {\n        if (!isListItem(element)) {\n            return;\n        }\n\n        if (\n            element.firstChild?.nodeType === Node.ELEMENT_NODE &&\n            isListElement(element.firstChild)\n        ) {\n            element.classList.add(\"oe-nested\");\n        }\n\n        if (\n            [...element.children].some(\n                (child) => isBlock(child) && !this.dependencies.split.isUnsplittable(child)\n            )\n        ) {\n            const cursors = this.dependencies.selection.preserveSelection();\n            wrapInlinesInBlocks(element, {\n                baseContainerNodeName: this.dependencies.baseContainer.getDefaultNodeName(),\n                cursors,\n            });\n            cursors.restore();\n        }\n    }\n\n    normalizeNestedList(element) {\n        if (element.tagName === \"LI\") {\n            return;\n        }\n        if ([\"UL\", \"OL\"].includes(element.parentElement?.tagName)) {\n            const cursors = this.dependencies.selection.preserveSelection();\n            let li;\n            if (element.previousElementSibling?.nodeName === \"LI\") {\n                li = element.previousElementSibling;\n            } else {\n                li = this.document.createElement(\"li\");\n                li.classList.add(\"oe-nested\");\n            }\n            element.parentElement.insertBefore(li, element);\n            li.appendChild(element);\n            cursors.restore();\n        }\n    }\n\n    // --------------------------------------------------------------------------\n    // Indentation\n    // --------------------------------------------------------------------------\n\n    // @temp comment: former oTab\n    /**\n     * @param {HTMLLIElement} li\n     */\n    indentLI(li) {\n        const lip = li.previousElementSibling || this.document.createElement(\"li\");\n        if (!lip.hasChildNodes()) {\n            lip.classList.add(\"oe-nested\");\n        }\n        const parentLi = li.parentElement;\n        const nextSiblingLi = li.nextSibling;\n        const destul =\n            li.previousElementSibling?.querySelector(\"ol, ul\") ||\n            li.querySelector(\"ol, ul\") ||\n            li.closest(\"ol, ul\");\n        const cursors = this.dependencies.selection.preserveSelection();\n        // Remove the LI first to force a removal mutation in collaboration.\n        parentLi.removeChild(li);\n        const ul = createList(this.document, this.getListMode(destul));\n        lip.append(ul);\n\n        // lip replaces li\n        li.before(lip);\n        ul.append(li);\n        const nestedLists = childNodes(li).filter((n) => isListElement(n));\n        ul.after(...nestedLists);\n        parentLi.insertBefore(lip, nextSiblingLi);\n        cursors.update((cursor) => {\n            if (cursor.node === lip.parentNode) {\n                const childIndex = childNodeIndex(lip);\n                if (cursor.offset === childIndex) {\n                    [cursor.node, cursor.offset] = [ul, 0];\n                } else if (cursor.offset === childIndex + 1) {\n                    [cursor.node, cursor.offset] = [ul, 1];\n                }\n            }\n        });\n        cursors.restore();\n    }\n\n    /**\n     * @param {HTMLLIElement} li\n     * @returns {HTMLLIElement|null} li or null if it no longer exists.\n     */\n    outdentLI(li) {\n        const listToSplit = li.querySelector(\"ol, ul\") || li.nextElementSibling;\n        if (listToSplit) {\n            this.splitList(listToSplit);\n        }\n\n        if (isListItem(li.parentNode.parentNode)) {\n            this.outdentNestedLI(li);\n            return li;\n        }\n        this.outdentTopLevelLI(li);\n        return null;\n    }\n\n    /**\n     * Splits a list at the given LI element (li is moved to the new list).\n     *\n     * @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} node - HTML element\n     */\n    splitList(node) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        // Create new list\n        const currentList = closestElement(node, \"ul, ol\");\n        const newList = currentList.cloneNode(false);\n        const isList = isListElement(node);\n        const wrapperLi = isList ? this.document.createElement(\"li\") : node;\n\n        if (isList) {\n            wrapperLi.classList.add(\"oe-nested\");\n            newList.append(wrapperLi);\n            cursors.update(callbacksForCursorUpdate.after(node.parentNode.parentNode, newList));\n            node.parentNode.parentNode.after(newList);\n        } else if (isListItem(node.parentNode.parentNode)) {\n            // li is nested list item\n            const lip = this.document.createElement(\"li\");\n            lip.classList.add(\"oe-nested\");\n            lip.append(newList);\n            cursors.update(callbacksForCursorUpdate.after(node.parentNode.parentNode, lip));\n            node.parentNode.parentNode.after(lip);\n        } else {\n            cursors.update(callbacksForCursorUpdate.after(node.parentNode, newList));\n            node.parentNode.after(newList);\n        }\n\n        const moveFrom = isList ? node.parentElement : node;\n        while (moveFrom.nextSibling) {\n            cursors.update(callbacksForCursorUpdate.append(newList, moveFrom.nextSibling));\n            newList.append(moveFrom.nextSibling);\n        }\n\n        const moveTo = isList ? wrapperLi : newList;\n        cursors.update(callbacksForCursorUpdate.prepend(moveTo, node));\n        moveTo.prepend(node);\n        cursors.restore();\n        this.adjustListPadding(currentList);\n        this.adjustListPadding(newList);\n        return newList;\n    }\n\n    outdentNestedLI(li) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        const ul = li.parentNode;\n        const lip = ul.parentNode;\n        // Move LI\n        cursors.update(callbacksForCursorUpdate.after(lip, li));\n        lip.after(li);\n        while (ul.nextSibling) {\n            cursors.update(callbacksForCursorUpdate.append(li, ul.nextSibling));\n            li.append(ul.nextSibling);\n        }\n        // Remove UL and LI.oe-nested if left empty.\n        if (!ul.children.length) {\n            cursors.update(callbacksForCursorUpdate.remove(ul));\n            ul.remove();\n        }\n        // @todo @phoenix: not sure in which scenario lip would not have\n        // oe-nested class\n        if (!lip.children.length && lip.classList.contains(\"oe-nested\")) {\n            cursors.update(callbacksForCursorUpdate.remove(lip));\n            lip.remove();\n        }\n        this.adjustListPadding(li.parentElement);\n        cursors.restore();\n    }\n\n    /**\n     * @param {HTMLLIElement} li\n     */\n    outdentTopLevelLI(li) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        const ul = li.parentNode;\n        const children = childNodes(li);\n        if (!children.every(isBlock)) {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            for (const child of children) {\n                cursors.update(callbacksForCursorUpdate.append(baseContainer, child));\n                baseContainer.append(child);\n            }\n            if (isShrunkBlock(baseContainer)) {\n                baseContainer.append(this.document.createElement(\"br\"));\n            }\n            li.append(baseContainer);\n            cursors.remapNode(li, baseContainer);\n        }\n        // Move LI's children to after UL\n        const blocksToMove = childNodes(li);\n        for (const block of blocksToMove.toReversed()) {\n            cursors.update(callbacksForCursorUpdate.after(ul, block));\n            ul.after(block);\n        }\n        // Preserve style properties\n        const dir = li.getAttribute(\"dir\") || ul.getAttribute(\"dir\");\n        const textAlign = li.style.getPropertyValue(\"text-align\");\n        const liColorStyle = getTextColorOrClass(li);\n        const liFontSizeStyle = getFontSizeOrClass(li);\n        const wrapChildren = (parent, tag) => {\n            const wrapper = this.document.createElement(tag);\n            wrapper.append(...parent.childNodes);\n            parent.replaceChildren(wrapper);\n            cursors.remapNode(parent, wrapper);\n            return wrapper;\n        };\n        for (const block of blocksToMove) {\n            // text direction\n            if (dir && !block.getAttribute(\"dir\")) {\n                block.setAttribute(\"dir\", dir);\n            }\n            // text alignment\n            if (textAlign && !block.style.getPropertyValue(\"text-align\")) {\n                block.style.setProperty(\"text-align\", textAlign);\n            }\n            // text color\n            if (liColorStyle) {\n                const font = wrapChildren(block, \"font\");\n                this.dependencies.color.colorElement(font, liColorStyle.value, \"color\");\n            }\n            // font-size\n            if (liFontSizeStyle && !isEmptyBlock(block)) {\n                const span = wrapChildren(block, \"span\");\n                if (liFontSizeStyle.type === \"font-size\") {\n                    span.style.fontSize = liFontSizeStyle.value;\n                } else if (liFontSizeStyle.type === \"class\") {\n                    span.classList.add(liFontSizeStyle.value);\n                }\n            }\n        }\n        // Remove LI\n        cursors.update(callbacksForCursorUpdate.remove(li));\n        li.remove();\n        // Remove UL if left empty\n        if (!ul.firstElementChild) {\n            cursors.update(callbacksForCursorUpdate.remove(ul));\n            ul.remove();\n        } else {\n            this.adjustListPadding(ul);\n        }\n        cursors.restore();\n    }\n\n    indentListNodes(listNodes) {\n        for (const li of listNodes) {\n            this.indentLI(li);\n        }\n    }\n\n    outdentListNodes(listNodes) {\n        for (const li of listNodes) {\n            this.outdentLI(li);\n        }\n    }\n\n    separateListItems() {\n        const listItems = new Set();\n        const navListItems = new Set();\n        const nonListItems = [];\n        const blocks = [...this.dependencies.selection.getTargetedBlocks()].filter(\n            (n) => !n.querySelector(\"li\")\n        );\n        for (const block of blocks) {\n            const closestLI = block.closest(\"li\");\n            if (closestLI) {\n                if (closestLI.classList.contains(\"nav-item\")) {\n                    navListItems.add(closestLI);\n                } else if (closestLI.isContentEditable) {\n                    listItems.add(closestLI);\n                }\n            } else if (![\"UL\", \"OL\"].includes(block.tagName)) {\n                nonListItems.push(block);\n            }\n        }\n        return { listItems: [...listItems], navListItems: [...navListItems], nonListItems };\n    }\n\n    // --------------------------------------------------------------------------\n    // Handlers of other plugins commands\n    // --------------------------------------------------------------------------\n\n    processNodeToInsert({ nodeToInsert, container }) {\n        if (isListItemElement(container) && isParagraphRelatedElement(nodeToInsert)) {\n            nodeToInsert = this.dependencies.dom.setTagName(nodeToInsert, \"LI\");\n        }\n        const listEl = container && closestElement(container, listElementSelector);\n        if (!listEl) {\n            return nodeToInsert;\n        }\n        const mode = container && this.getListMode(listEl);\n        if (isListItemElement(nodeToInsert) && nodeToInsert.querySelector(\"ol, ul\")) {\n            return this.convertList(nodeToInsert, mode);\n        }\n        if (isListElement(nodeToInsert)) {\n            return this.convertList(nodeToInsert, this.getListMode(nodeToInsert));\n        }\n        return nodeToInsert;\n    }\n\n    handleTab() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const closestLI = closestElement(selection.anchorNode, \"LI\");\n        if (closestLI) {\n            const block = closestBlock(selection.anchorNode);\n            const isLiContainsUnSpittable =\n                isParagraphRelatedElement(block) &&\n                ancestors(block, closestLI).find((node) =>\n                    this.dependencies.split.isUnsplittable(node)\n                );\n            if (isLiContainsUnSpittable) {\n                return;\n            }\n        }\n        const { listItems, navListItems, nonListItems } = this.separateListItems();\n        if (listItems.length || navListItems.length) {\n            this.indentListNodes(listItems);\n            this.dependencies.tabulation.indentBlocks(nonListItems);\n            const listsToAdjustPadding = new Set(\n                listItems.map((li) => closestElement(li, \"ul, ol\")).filter(Boolean)\n            );\n            for (const list of listsToAdjustPadding) {\n                this.adjustListPadding(list);\n            }\n            // Do nothing to nav-items.\n            this.dependencies.history.addStep();\n            return true;\n        }\n    }\n\n    handleShiftTab() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const closestLI = closestElement(selection.anchorNode, \"LI\");\n        if (closestLI) {\n            const block = closestBlock(selection.anchorNode);\n            const isLiContainsUnSpittable =\n                isParagraphRelatedElement(block) &&\n                ancestors(block, closestLI).find((node) =>\n                    this.dependencies.split.isUnsplittable(node)\n                );\n            if (isLiContainsUnSpittable) {\n                return;\n            }\n        }\n        const { listItems, navListItems, nonListItems } = this.separateListItems();\n        if (listItems.length || navListItems.length) {\n            this.outdentListNodes(listItems);\n            this.dependencies.tabulation.outdentBlocks(nonListItems);\n            // Do nothing to nav-items.\n            this.dependencies.history.addStep();\n            return true;\n        }\n    }\n\n    handleSplitBlock(params) {\n        const closestLI = closestElement(params.targetNode, \"LI\");\n        const isBlockUnsplittable =\n            closestLI &&\n            Array.from(closestLI.childNodes).some(\n                (node) => isBlock(node) && this.dependencies.split.isUnsplittable(node)\n            );\n        if (!closestLI || isBlockUnsplittable) {\n            return;\n        }\n        if (isEmptyBlock(closestLI)) {\n            this.outdentLI(closestLI);\n            return true;\n        }\n        const [, newLI] = this.dependencies.split.splitElementBlock({\n            ...params,\n            blockToSplit: closestLI,\n        });\n        if (newLI) {\n            if (closestLI.classList.contains(\"o_checked\")) {\n                removeClass(newLI, \"o_checked\");\n            }\n            const [anchorNode, anchorOffset] = getDeepestPosition(newLI, 0);\n            this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n            this.adjustListPadding(newLI.parentElement);\n        }\n        return true;\n    }\n\n    /**\n     * Fully outdent list item if cursor is at its beginning.\n     */\n    handleDeleteBackward(range) {\n        const { startContainer, startOffset, endContainer, endOffset } = range;\n        const closestLIendContainer = closestElement(endContainer, \"LI\");\n        if (!closestLIendContainer) {\n            return;\n        }\n        // Detect if cursor is at beginning of LI (or the editable === collapsed range).\n        const isCursorAtStartofLI =\n            (startContainer === endContainer && startOffset === endOffset) ||\n            closestElement(startContainer, \"LI\") !== closestLIendContainer;\n        if (!isCursorAtStartofLI) {\n            return;\n        }\n        // Check if li or parent list(s) are unsplittable.\n        let element = closestLIendContainer;\n        while ([\"LI\", \"UL\", \"OL\"].includes(element.tagName)) {\n            if (this.dependencies.split.isUnsplittable(element)) {\n                return;\n            }\n            element = element.parentElement;\n        }\n        if (!closestLIendContainer.classList.contains(\"oe-nested\")) {\n            // Remove LI marker on first backspace.\n            closestLIendContainer.classList.add(\"oe-nested\");\n            closestLIendContainer.classList.remove(\"o_checked\");\n        } else {\n            // Fully outdent the LI but keep its direction.\n            const list = closestElement(closestLIendContainer, \"ul[dir], ol[dir]\");\n            const dir = list?.getAttribute(\"dir\");\n            if (dir) {\n                closestLIendContainer.setAttribute(\"dir\", dir);\n            }\n            this.liToBlocks(closestLIendContainer);\n        }\n        return true;\n    }\n\n    // Uncheck checklist item left empty after deleting a multi-LI selection.\n    handleDeleteRange(range) {\n        const { startContainer, endContainer } = range;\n        const startCheckedLi = closestElement(startContainer, \"li.o_checked\");\n        if (!startCheckedLi) {\n            return;\n        }\n        const endLi = closestElement(endContainer, \"li\");\n        if (startCheckedLi === endLi) {\n            return;\n        }\n\n        range = this.dependencies.delete.deleteRange(range);\n        this.dependencies.selection.setSelection({\n            anchorNode: range.startContainer,\n            anchorOffset: range.startOffset,\n        });\n\n        if (isEmptyBlock(startCheckedLi)) {\n            removeClass(startCheckedLi, \"o_checked\");\n        }\n\n        return true;\n    }\n\n    /**\n     * @param {DocumentFragment} clonedContents\n     * @param {import(\"@html_editor/core/selection_plugin\").EditorSelection} selection\n     */\n    processContentForClipboard(clonedContents, selection) {\n        if (clonedContents.firstChild.nodeName === \"LI\") {\n            const list = selection.commonAncestorContainer.cloneNode();\n            list.replaceChildren(...childNodes(clonedContents));\n            clonedContents = list;\n        }\n        return clonedContents;\n    }\n\n    insertListWithinPre(node) {\n        const listItems = node.querySelectorAll(\"li:not(.oe-nested)\");\n        for (const li of listItems) {\n            const nestingLvl = ancestors(li).filter(isListElement).length - 1;\n            const list = closestElement(li, \"ul, ol\");\n            const listMode = this.getListMode(list);\n            let char;\n            if (listMode === \"CL\") {\n                char = \"[] \";\n            } else if (listMode === \"OL\") {\n                const children = childNodes(li.parentElement).filter(\n                    (n) => !n.classList.contains(\"oe-nested\")\n                );\n                char = `${children.indexOf(li) + 1}. `;\n            } else {\n                char = \"* \";\n            }\n            const prefix = \" \".repeat(nestingLvl * 4) + char;\n            li.prepend(this.document.createTextNode(prefix));\n        }\n        return node;\n    }\n\n    // --------------------------------------------------------------------------\n    // Event handlers\n    // --------------------------------------------------------------------------\n\n    /**\n     * @param {MouseEvent | TouchEvent} ev\n     */\n    onPointerdown(ev) {\n        const node = ev.target;\n        const isChecklistItem =\n            node.tagName == \"LI\" && this.getListMode(node.parentElement) == \"CL\";\n        if (!isChecklistItem) {\n            return;\n        }\n        let offsetX = ev.offsetX;\n        let offsetY = ev.offsetY;\n        if (ev.type === \"touchstart\") {\n            const rect = node.getBoundingClientRect();\n            offsetX = ev.touches[0].clientX - rect.x;\n            offsetY = ev.touches[0].clientY - rect.y;\n        }\n\n        if (isChecklistItem && this.isPointerInsideCheckbox(node, offsetX, offsetY)) {\n            toggleClass(node, \"o_checked\");\n            const { documentSelectionIsInEditable } =\n                this.dependencies.selection.getSelectionData();\n            // When the editable is not focused, clicking on checkbox\n            // wont make it focused So changes will be lost\n            // as no blur event will occur when clicking outside.\n            if (!documentSelectionIsInEditable) {\n                this.editable.focus();\n                this.dependencies.selection.setSelection({ anchorNode: node, anchorOffset: 0 });\n            }\n            ev.preventDefault();\n            this.dependencies.history.addStep();\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLLIElement} li - LI element inside a checklist.\n     */\n    isPointerInsideCheckbox(li, pointerOffsetX, pointerOffsetY) {\n        const beforeStyle = this.window.getComputedStyle(li, \":before\");\n        const checkboxPosition = {\n            left: parseInt(beforeStyle.left),\n            top: parseInt(beforeStyle.top),\n        };\n        checkboxPosition.right = checkboxPosition.left + parseInt(beforeStyle.width);\n        checkboxPosition.bottom = checkboxPosition.top + parseInt(beforeStyle.height);\n\n        return (\n            pointerOffsetX >= checkboxPosition.left &&\n            pointerOffsetX <= checkboxPosition.right &&\n            pointerOffsetY >= checkboxPosition.top &&\n            pointerOffsetY <= checkboxPosition.bottom\n        );\n    }\n\n    applyColorToListItem(color, mode) {\n        this.dependencies.split.splitSelection();\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const listItems = new Set(\n            targetedNodes.map((n) => closestElement(n, \"li\")).filter(Boolean)\n        );\n        if (!listItems.size || mode !== \"color\" || isColorGradient(color)) {\n            return;\n        }\n        const cursors = this.dependencies.selection.preserveSelection();\n        for (const listItem of listItems) {\n            if (this.dependencies.selection.areNodeContentsFullySelected(listItem)) {\n                for (const node of [\n                    listItem,\n                    ...descendants(listItem).filter(\n                        (n) => isElement(n) && closestElement(n, \"LI\") === listItem\n                    ),\n                ]) {\n                    // Remove any color-related classes.\n                    const classesToRemove = [...node.classList].filter(\n                        (cls) => cls === \"o_default_color\" || TEXT_CLASSES_REGEX.test(cls)\n                    );\n                    removeClass(node, ...classesToRemove);\n\n                    if (node.style.color) {\n                        removeStyle(node, \"color\");\n                    }\n                }\n\n                if (color) {\n                    this.dependencies.color.colorElement(listItem, color, mode);\n                    const sublists = childNodes(listItem).filter(isListElement);\n                    for (const list of sublists) {\n                        list.classList.add(\"o_default_color\");\n                    }\n                }\n            } else if (\n                color === \"\" &&\n                (listItem.style.color ||\n                    [...listItem.classList].some((cls) => TEXT_CLASSES_REGEX.test(cls)))\n            ) {\n                const textNodes = targetedNodes.filter(\n                    (n) => isVisibleTextNode(n) && closestElement(n, \"li\") === listItem\n                );\n                // Remove inline color from partial selection by\n                // wrapping in font with default color.\n                for (const node of textNodes) {\n                    const font = this.document.createElement(\"font\");\n                    font.classList.add(\"o_default_color\");\n                    node.before(font);\n                    cursors.update(callbacksForCursorUpdate.before(node, font));\n                    font.append(node);\n                    cursors.update(callbacksForCursorUpdate.append(font, node));\n                }\n            }\n        }\n        cursors.restore();\n    }\n\n    applyFormatToListItem(formatName, { formatProps, applyStyle } = {}) {\n        if (![\"setFontSizeClassName\", \"fontSize\"].includes(formatName)) {\n            return;\n        }\n        this.dependencies.split.splitSelection();\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const listItems = new Set(\n            targetedNodes.map((n) => closestElement(n, \"li\")).filter(Boolean)\n        );\n        if (!listItems.size) {\n            return false;\n        }\n        const listsSet = new Set();\n        const cursors = this.dependencies.selection.preserveSelection();\n        for (const listItem of listItems) {\n            // Skip list items with block descendants other than base\n            // container or a list related elements or no font size formatting\n            // to remove.\n            const hasOnlyBaseBlocks = [...descendants(listItem)]\n                .filter(isBlock)\n                .every((n) => n.matches(`${baseContainerGlobalSelector}, ol, ul, li`));\n            const hasExistingFontSize =\n                FONT_SIZE_CLASSES.some((c) => listItem.classList.contains(c)) ||\n                listItem.style.fontSize;\n            if (!hasOnlyBaseBlocks || (!applyStyle && !hasExistingFontSize)) {\n                continue;\n            }\n\n            if (this.dependencies.selection.areNodeContentsFullySelected(listItem)) {\n                for (const node of [\n                    listItem,\n                    ...descendants(listItem).filter(\n                        (n) => isElement(n) && closestElement(n, \"LI\") === listItem\n                    ),\n                ]) {\n                    removeClass(node, ...FONT_SIZE_CLASSES, \"o_default_font_size\");\n                    if (node.style.fontSize) {\n                        node.style.fontSize = \"\";\n                    }\n                }\n\n                if (applyStyle) {\n                    if (formatName === \"setFontSizeClassName\") {\n                        listItem.classList.add(formatProps.className);\n                    } else if (formatName === \"fontSize\") {\n                        listItem.style.fontSize = formatProps.size;\n                    }\n                    const sublists = childNodes(listItem).filter(isListElement);\n                    for (const list of sublists) {\n                        list.classList.add(\"o_default_font_size\");\n                    }\n                }\n            } else if (!applyStyle && hasExistingFontSize) {\n                const textNodes = targetedNodes.filter(\n                    (n) => isVisibleTextNode(n) && closestElement(n, \"li\") === listItem\n                );\n                // Remove inline font size from partial selection by\n                // wrapping in span with default font size.\n                for (const node of textNodes) {\n                    const span = this.document.createElement(\"span\");\n                    span.classList.add(\"o_default_font_size\");\n                    node.before(span);\n                    cursors.update(callbacksForCursorUpdate.before(node, span));\n                    span.append(node);\n                    cursors.update(callbacksForCursorUpdate.append(span, node));\n                }\n            }\n            listsSet.add(listItem.parentElement);\n        }\n        cursors.restore();\n        for (const list of listsSet) {\n            this.adjustListPadding(list);\n        }\n        return true;\n    }\n\n    /**\n     * Adjusts the left padding of a list (`ul` or `ol`) to ensure that\n     * its `::marker` is always visible and doesn't overflow, especially\n     * when the marker width exceeds the default padding.\n     *\n     * @param {HTMLElement} list - The `<ul>` element used to determine the parent list and marker width.\n     */\n    adjustListPadding(list) {\n        if (!isListElement(list)) {\n            return;\n        }\n        list.style.removeProperty(\"padding-inline-start\");\n        if (list.classList.contains(\"o_checklist\")) {\n            return;\n        }\n\n        const largestMarker = list.children[Symbol.iterator]()\n            .map((li) => {\n                const markerWidth = parseFloat(this.window.getComputedStyle(li, \"::marker\").width);\n                return isNaN(markerWidth) ? 0 : markerWidth;\n            })\n            .reduce((accumulator, currentValue) => Math.max(accumulator, currentValue));\n        // For `UL` with large font size the marker width is so big that more padding is needed.\n        const largestMarkerPadding = Math.round(largestMarker) * (list.nodeName === \"UL\" ? 2 : 1);\n\n        // bootstrap sets ul { padding-left: 2rem; }\n        const defaultPadding = parseFloat(getHtmlStyle(this.document).fontSize) * 2;\n        // Align the whole list based on the item that requires the largest padding.\n        // For smaller font sizes, doubling the width of the dot marker is still lower than the\n        // default. The default is kept in that case.\n        if (largestMarkerPadding > defaultPadding) {\n            list.style.paddingInlineStart = `${largestMarkerPadding}px`;\n        }\n    }\n\n    adjustListPaddingOnDelete() {\n        const selection = this.document.getSelection();\n        if (!selection.isCollapsed || !selection.anchorNode) {\n            return;\n        }\n        const listItem = closestElement(selection.anchorNode);\n        if (isListItem(listItem)) {\n            this.adjustListPadding(listItem.parentElement);\n        }\n    }\n\n    // --------------------------------------------------------------------------\n    // Toolbar buttons\n    // --------------------------------------------------------------------------\n\n    updateToolbarButtons() {\n        this.toolbarListSelectorKey.value++;\n    }\n\n    getListSelectorButtons() {\n        return listSelectorItems\n            .filter((item) => item.commandId != \"toggleListCL\" || this.config.allowChecklist)\n            .map((item) => {\n                const command = this.resources.user_commands.find(\n                    (cmd) => cmd.id === item.commandId\n                );\n                const button = composeToolbarButton(command, item);\n                return {\n                    ...pick(button, \"id\", \"icon\", \"run\", \"mode\"),\n                    // We want short descriptions for these buttons.\n                    description: command.title,\n                };\n            });\n    }\n}\n", "import { Component } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { toolbarButtonProps } from \"../toolbar/toolbar\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nexport class ListSelector extends Component {\n    static template = \"html_editor.ListSelector\";\n    static props = {\n        ...toolbarButtonProps,\n        getButtons: Function,\n        getListMode: Function,\n        key: Object,\n    };\n    static components = { Dropdown };\n\n    setup() {\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n    }\n    getActiveMode() {\n        const { editableSelection: selection } = this.props.getSelection();\n        const closestLI = closestElement(selection.anchorNode, \"LI\");\n        return closestLI && this.props.getListMode(closestLI.parentNode);\n    }\n}\n", "import { unwrapContents } from \"@html_editor/utils/dom\";\nimport { closestElement, firstLeaf, lastLeaf } from \"@html_editor/utils/dom_traversal\";\nimport { getFontSizeOrClass } from \"@html_editor/utils/formatting\";\n\nexport function createList(document, mode) {\n    const node = document.createElement(mode === \"OL\" ? \"OL\" : \"UL\");\n    if (mode === \"CL\") {\n        node.classList.add(\"o_checklist\");\n    }\n    return node;\n}\n\nexport function insertListAfter(document, afterNode, mode, content = []) {\n    const list = createList(document, mode);\n    afterNode.after(list);\n    const li = document.createElement(\"LI\");\n    li.append(...content);\n    if (content.length === 1 && content[0].nodeType === Node.ELEMENT_NODE) {\n        const firstLeafNode = firstLeaf(content[0]);\n        const lastLeafNode = lastLeaf(content[0]);\n        const firstClosestFont = closestElement(firstLeafNode, \"font\");\n        const lastClosestFont = closestElement(lastLeafNode, \"font\");\n        if (firstClosestFont && lastClosestFont && firstClosestFont === lastClosestFont) {\n            li.style.color = firstClosestFont.style.color;\n            unwrapContents(firstClosestFont);\n        }\n        const firstClosestSpan = closestElement(firstLeafNode, \"span\");\n        const lastClosestSpan = closestElement(lastLeafNode, \"span\");\n        let fontSizeStyle;\n        if (\n            firstClosestSpan &&\n            lastClosestSpan &&\n            firstClosestSpan === lastClosestSpan &&\n            (fontSizeStyle = getFontSizeOrClass(firstClosestSpan))\n        ) {\n            if (fontSizeStyle.type === \"font-size\") {\n                li.style.fontSize = fontSizeStyle.value;\n            } else if (fontSizeStyle.type === \"class\") {\n                li.classList.add(fontSizeStyle.value);\n            }\n            unwrapContents(firstClosestSpan);\n        }\n    }\n    list.append(li);\n    return list;\n}\n\n/* Returns true if the two lists are of the same type among:\n * - OL\n * - regular UL\n * - checklist (ul.o_checklist)\n * - container for nested lists (li.oe-nested)\n */\nexport function compareListTypes(a, b) {\n    if (!a || !b || a.tagName !== b.tagName) {\n        return false;\n    }\n    if (a.classList.contains(\"o_checklist\") !== b.classList.contains(\"o_checklist\")) {\n        return false;\n    }\n    if (a.tagName === \"LI\") {\n        if (a.classList.contains(\"oe-nested\") !== b.classList.contains(\"oe-nested\")) {\n            return false;\n        }\n        return compareListTypes(a.firstElementChild, b.firstElementChild);\n    }\n    return true;\n}\n\nexport function isListItem(node) {\n    return node.nodeName === \"LI\" && !node.classList.contains(\"nav-item\");\n}\n", "import { Plugin } from \"../plugin\";\n\n/**\n * @typedef { Object } LocalOverlayShared\n * @property { LocalOverlayPlugin['makeLocalOverlay'] } makeLocalOverlay\n */\n\n/**\n * This plugins provides a way to create a \"local\" overlays so that their\n * visibility is relative to the overflow of their ancestors.\n */\nexport class LocalOverlayPlugin extends Plugin {\n    static id = \"localOverlay\";\n    static shared = [\"makeLocalOverlay\"];\n\n    setup() {\n        this.localOverlayContainer = this.config.localOverlayContainers?.ref.el;\n    }\n\n    /**\n     * Make a local container to organise floating elements inside it's own\n     * box and z-index isolation.\n     *\n     * @param {string} containerId An id to add to the container in order to make\n     *              the container more visible in the devtool and potentially\n     *              add css rules for the container and it's children.\n     */\n    makeLocalOverlay(containerId) {\n        const container = this.document.createElement(\"div\");\n        container.className = `oe-local-overlay`;\n        container.setAttribute(\"data-oe-local-overlay-id\", containerId);\n        if (this.localOverlayContainer) {\n            this.localOverlayContainer.append(container);\n        }\n        return container;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\n\nexport class DoubleClickImagePreviewPlugin extends Plugin {\n    static id = \"dblclickImagePreview\";\n    static dependencies = [\"image\"];\n\n    setup() {\n        this.addDomListener(this.editable, \"dblclick\", (e) => {\n            if (e.target.tagName === \"IMG\") {\n                this.dependencies.image.previewImage();\n            }\n        });\n    }\n}\n", "import {\n    DocumentSelector,\n    renderStaticFileBox,\n} from \"@html_editor/main/media/media_dialog/document_selector\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport class FilePlugin extends Plugin {\n    static id = \"file\";\n    static dependencies = [\"dom\", \"history\"];\n    static defaultConfig = {\n        allowFile: true,\n    };\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: {\n            id: \"uploadFile\",\n            title: _t(\"Upload a file\"),\n            description: _t(\"Add a download box\"),\n            icon: \"fa-upload\",\n            run: this.uploadAndInsertFiles.bind(this),\n            isAvailable: (selection) =>\n                this.isUploadCommandAvailable(selection) && isHtmlContentSupported(selection),\n        },\n        powerbox_items: {\n            categoryId: \"media\",\n            commandId: \"uploadFile\",\n            keywords: [_t(\"file\"), _t(\"document\")],\n        },\n        power_buttons: withSequence(5, {\n            commandId: \"uploadFile\",\n            description: _t(\"Upload a file\"),\n        }),\n        unsplittable_node_predicates: (node) => node.classList?.contains(\"o_file_box\"),\n        ...(this.config.allowFile &&\n            this.config.allowMediaDocuments && {\n                media_dialog_extra_tabs: {\n                    id: \"DOCUMENTS\",\n                    title: _t(\"Documents\"),\n                    Component: this.componentForMediaDialog,\n                    sequence: 15,\n                },\n            }),\n        selectors_for_feff_providers: () => \".o_file_box\",\n        functional_empty_node_predicates: (node) =>\n            node?.nodeName === \"SPAN\" && node.classList.contains(\"o_file_box\"),\n        is_node_editable_predicates: (node) => {\n            if (node?.nodeName === \"SPAN\" && node.classList.contains(\"o_file_box\")) {\n                return false;\n            }\n        },\n    };\n\n    get recordInfo() {\n        return this.config.getRecordInfo?.() || {};\n    }\n\n    isUploadCommandAvailable() {\n        return this.config.allowFile;\n    }\n\n    get componentForMediaDialog() {\n        return DocumentSelector;\n    }\n\n    async uploadAndInsertFiles() {\n        // Upload\n        const attachments = await this.services.uploadLocalFiles.upload(this.recordInfo, {\n            multiple: true,\n            accessToken: true,\n        });\n        if (!attachments.length) {\n            // No files selected or error during upload\n            this.editable.focus();\n            return;\n        }\n        if (this.config.onAttachmentChange) {\n            attachments.forEach(this.config.onAttachmentChange);\n        }\n        // Render\n        const fileCards = attachments.map(this.renderDownloadBox.bind(this));\n        // Insert\n        fileCards.forEach(this.dependencies.dom.insert);\n        this.dependencies.history.addStep();\n    }\n\n    renderDownloadBox(attachment) {\n        const url = this.services.uploadLocalFiles.getURL(attachment, {\n            download: true,\n            unique: true,\n            accessToken: true,\n        });\n        const { name: filename, mimetype, id } = attachment;\n        return renderStaticFileBox(filename, mimetype, url, id);\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { ColorSelector } from \"../font/color_selector\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport class IconColorPlugin extends Plugin {\n    static id = \"iconColor\";\n    static dependencies = [\"icon\", \"colorUi\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        toolbar_groups: withSequence(1, { id: \"icon_color\", namespaces: [\"icon\"] }),\n        toolbar_items: [\n            {\n                id: \"icon_forecolor\",\n                groupId: \"icon_color\",\n                description: _t(\"Select Font Color\"),\n                Component: ColorSelector,\n                props: this.dependencies.colorUi.getPropsForColorSelector(\"foreground\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"icon_backcolor\",\n                groupId: \"icon_color\",\n                description: _t(\"Select Background Color\"),\n                Component: ColorSelector,\n                props: this.dependencies.colorUi.getPropsForColorSelector(\"background\"),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n    };\n}\n", "import { withSequence } from \"@html_editor/utils/resource\";\nimport { Plugin } from \"../../plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { MediaDialog } from \"./media_dialog/media_dialog\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { ICON_SELECTOR, isElement } from \"@html_editor/utils/dom_info\";\n\nexport class IconPlugin extends Plugin {\n    static id = \"icon\";\n    static dependencies = [\"history\", \"selection\", \"dialog\"];\n    toolbarNamespace = \"icon\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"resizeIcon1\",\n                description: _t(\"Resize icon 1x\"),\n                run: () => this.resizeIcon({ size: \"1\" }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"resizeIcon2\",\n                description: _t(\"Resize icon 2x\"),\n                run: () => this.resizeIcon({ size: \"2\" }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"resizeIcon3\",\n                description: _t(\"Resize icon 3x\"),\n                run: () => this.resizeIcon({ size: \"3\" }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"resizeIcon4\",\n                description: _t(\"Resize icon 4x\"),\n                run: () => this.resizeIcon({ size: \"4\" }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"resizeIcon5\",\n                description: _t(\"Resize icon 5x\"),\n                run: () => this.resizeIcon({ size: \"5\" }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"toggleSpinIcon\",\n                description: _t(\"Toggle icon spin\"),\n                icon: \"fa-play\",\n                run: this.toggleSpinIcon.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"replaceIcon\",\n                description: _t(\"Replace icon\"),\n                run: this.openIconDialog.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        toolbar_namespace_providers: [\n            (targetedNodes) => {\n                if (\n                    targetedNodes.length &&\n                    targetedNodes.every(\n                        // All nodes should be icons, its ZWS child or its ancestors\n                        (node) =>\n                            node.classList?.contains(\"fa\") ||\n                            node.parentElement.classList.contains(\"fa\") ||\n                            (node.querySelector?.(\".fa\") && node.isContentEditable !== false)\n                    )\n                ) {\n                    return this.toolbarNamespace;\n                }\n            },\n        ],\n        toolbar_groups: [\n            withSequence(2, { id: \"icon_size\", namespaces: [\"icon\"] }),\n            withSequence(3, { id: \"icon_spin\", namespaces: [\"icon\"] }),\n            withSequence(3, { id: \"icon_replace\", namespaces: [\"icon\"] }),\n        ],\n        toolbar_items: [\n            {\n                id: \"icon_size_1\",\n                groupId: \"icon_size\",\n                commandId: \"resizeIcon1\",\n                text: \"1x\",\n                isActive: () => this.hasIconSize(\"1\"),\n            },\n            {\n                id: \"icon_size_2\",\n                groupId: \"icon_size\",\n                commandId: \"resizeIcon2\",\n                text: \"2x\",\n                isActive: () => this.hasIconSize(\"2\"),\n            },\n            {\n                id: \"icon_size_3\",\n                groupId: \"icon_size\",\n                commandId: \"resizeIcon3\",\n                text: \"3x\",\n                isActive: () => this.hasIconSize(\"3\"),\n            },\n            {\n                id: \"icon_size_4\",\n                groupId: \"icon_size\",\n                commandId: \"resizeIcon4\",\n                text: \"4x\",\n                isActive: () => this.hasIconSize(\"4\"),\n            },\n            {\n                id: \"icon_size_5\",\n                groupId: \"icon_size\",\n                commandId: \"resizeIcon5\",\n                text: \"5x\",\n                isActive: () => this.hasIconSize(\"5\"),\n            },\n            {\n                id: \"icon_spin\",\n                groupId: \"icon_spin\",\n                commandId: \"toggleSpinIcon\",\n                isActive: () => this.hasSpinIcon(),\n            },\n            {\n                id: \"icon_replace\",\n                groupId: \"icon_replace\",\n                commandId: \"replaceIcon\",\n                text: _t(\"Replace\"),\n            },\n        ],\n    };\n\n    getTargetedIcon() {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        return targetedNodes.find((node) => isElement(node) && node.matches(ICON_SELECTOR));\n    }\n\n    resizeIcon({ size }) {\n        const targetedIcon = this.getTargetedIcon();\n        if (!targetedIcon) {\n            return;\n        }\n        for (const classString of targetedIcon.classList) {\n            if (classString.match(/^fa-[2-5]x$/)) {\n                targetedIcon.classList.remove(classString);\n            }\n        }\n        if (size !== \"1\") {\n            targetedIcon.classList.add(`fa-${size}x`);\n        }\n        this.dependencies.history.addStep();\n    }\n\n    toggleSpinIcon() {\n        const selectedIcon = this.getTargetedIcon();\n        if (!selectedIcon) {\n            return;\n        }\n        selectedIcon.classList.toggle(\"fa-spin\");\n        this.dependencies.history.addStep();\n    }\n\n    hasIconSize(size) {\n        const selectedIcon = this.getTargetedIcon();\n        if (!selectedIcon) {\n            return;\n        }\n        if (size === \"1\") {\n            return ![...selectedIcon.classList].some((classString) =>\n                classString.match(/^fa-[2-5]x$/)\n            );\n        }\n        return selectedIcon.classList.contains(`fa-${size}x`);\n    }\n\n    hasSpinIcon() {\n        const selectedIcon = this.getTargetedIcon();\n        if (!selectedIcon) {\n            return;\n        }\n        return selectedIcon.classList.contains(\"fa-spin\");\n    }\n\n    openIconDialog() {\n        const selectedIcon = this.getTargetedIcon();\n        if (!selectedIcon) {\n            return;\n        }\n        this.dependencies.dialog.addDialog(MediaDialog, {\n            visibleTabs: [\"ICONS\"],\n            media: selectedIcon,\n            save: (el) => this.onSaveIcon(el, selectedIcon),\n        });\n    }\n\n    onSaveIcon(icon, prevIcon) {\n        for (const attribute of icon.attributes) {\n            prevIcon.setAttribute(attribute.nodeName, attribute.nodeValue);\n        }\n        this.dependencies.history.addStep();\n    }\n}\n", "import {\n    activateCropper,\n    loadImage,\n    loadImageInfo,\n    cropperDataFieldsWithAspectRatio,\n} from \"@html_editor/utils/image_processing\";\nimport { IMAGE_SHAPES } from \"./image_plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport {\n    Component,\n    useRef,\n    onMounted,\n    onWillDestroy,\n    markup,\n    useExternalListener,\n    status,\n} from \"@odoo/owl\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { scrollTo, closestScrollableY } from \"@web/core/utils/scrolling\";\n\nexport const cropperAspectRatios = {\n    \"0/0\": { label: _t(\"Flexible\"), value: 0 },\n    \"16/9\": { label: \"16:9\", value: 16 / 9 },\n    \"4/3\": { label: \"4:3\", value: 4 / 3 },\n    \"1/1\": { label: \"1:1\", value: 1 },\n    \"2/3\": { label: \"2:3\", value: 2 / 3 },\n};\n\nexport class ImageCrop extends Component {\n    static template = \"html_editor.ImageCrop\";\n    static props = {\n        document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },\n        media: { optional: true },\n        onClose: { type: Function, optional: true },\n        onSave: { type: Function, optional: true },\n    };\n\n    setup() {\n        this.aspectRatios = cropperAspectRatios;\n        this.notification = useService(\"notification\");\n        this.media = this.props.media;\n        this.document = this.props.document;\n\n        this.elRef = useRef(\"el\");\n        this.cropperWrapper = useRef(\"cropperWrapper\");\n        this.imageRef = useRef(\"imageRef\");\n        this.isCropperActive = false;\n\n        // We use capture so that the handler is called before other editor handlers\n        // like save, such that we can restore the src before a save.\n        // We need to add event listeners to the owner document of the widget.\n        useExternalListener(this.document, \"mousedown\", this.onDocumentMousedown, {\n            capture: true,\n        });\n        useExternalListener(this.document, \"keydown\", this.onDocumentKeydown, {\n            capture: true,\n        });\n        useExternalListener(\n            this.document,\n            \"selectionchange\",\n            () => {\n                if (!this.props.media.isConnected) {\n                    this.closeCropper();\n                }\n            },\n            { capture: true }\n        );\n\n        onMounted(() => {\n            this.hasModifiedImageClass = this.media.classList.contains(\"o_modified_image_to_save\");\n            if (this.hasModifiedImageClass) {\n                this.media.classList.remove(\"o_modified_image_to_save\");\n            }\n            this.show();\n        });\n        onWillDestroy(this.closeCropper);\n    }\n\n    closeCropper() {\n        if (!this.isCropperActive && !this.forceClose) {\n            return;\n        }\n        this.cropper?.destroy?.();\n        this.media.setAttribute(\"src\", this.initialSrc);\n        if (\n            this.hasModifiedImageClass &&\n            !this.media.classList.contains(\"o_modified_image_to_save\")\n        ) {\n            this.media.classList.add(\"o_modified_image_to_save\");\n        }\n        this.props?.onClose?.();\n        this.isCropperActive = false;\n    }\n\n    /**\n     * Resets the crop\n     */\n    async reset() {\n        if (this.cropper) {\n            this.cropper.reset();\n            if (this.aspectRatio !== \"0/0\") {\n                this.aspectRatio = \"0/0\";\n                this.cropper.setAspectRatio(cropperAspectRatios[this.aspectRatio].value);\n            }\n            await this.save();\n        }\n    }\n\n    async show() {\n        if (this.isCropperActive) {\n            return;\n        }\n        // key: ratio identifier, label: displayed to user, value: used by cropper lib\n        const src = this.media.getAttribute(\"src\");\n        const data = { ...this.media.dataset };\n        this.initialSrc = src;\n        this.aspectRatio = data.aspectRatio || \"0/0\";\n\n        // todo: check that the mutations of loadImage are not problematic (they most probably are).\n        Object.assign(this.media.dataset, await loadImageInfo(this.media));\n        const isIllustration = /^\\/(?:html|web)_editor\\/shape\\/illustration\\//.test(\n            this.media.dataset.originalSrc\n        );\n        this.uncroppable = false;\n        if (this.media.dataset.originalSrc && !isIllustration) {\n            this.originalSrc = this.media.dataset.originalSrc;\n            this.originalId = this.media.dataset.originalId;\n        } else {\n            // Couldn't find an attachment: not croppable.\n            this.uncroppable = true;\n        }\n\n        if (this.uncroppable) {\n            this.notification.add(\n                markup(\n                    _t(\n                        \"This type of image is not supported for cropping.<br/>If you want to crop it, please first download it from the original source and upload it in Odoo.\"\n                    )\n                ),\n                {\n                    title: _t(\"This image is an external image\"),\n                    type: \"warning\",\n                }\n            );\n            this.forceClose = true;\n            return this.closeCropper();\n        }\n\n        await this.scrollToInvisibleImage();\n        // Replacing the src with the original's so that the layout is correct.\n        await loadImage(this.originalSrc, this.media);\n        if (status(this) !== \"mounted\") {\n            // Abort if the component has been destroyed in the meantime\n            // since `this.imageRef.el` is `null` when it is not mounted.\n            return;\n        }\n        const cropperImage = this.imageRef.el;\n        [cropperImage.style.width, cropperImage.style.height] = [\n            this.media.width + \"px\",\n            this.media.height + \"px\",\n        ];\n\n        const sel = this.document.getSelection();\n        sel && sel.removeAllRanges();\n\n        // Overlaying the cropper image over the real image\n        let offset = undefined;\n        if (!this.media.getClientRects().length) {\n            offset = { top: 0, left: 0 };\n        } else {\n            const rect = this.media.getBoundingClientRect();\n            offset = {\n                top: rect.top,\n                left: rect.left,\n            };\n        }\n\n        offset.left += parseInt(this.media.style.paddingLeft || 0);\n        offset.top += parseInt(this.media.style.paddingRight || 0);\n        const frameElement = this.media.ownerDocument.defaultView.frameElement;\n        if (frameElement) {\n            const frameRect = frameElement.getBoundingClientRect();\n            offset.left += frameRect.left;\n            offset.top += frameRect.top;\n        }\n\n        this.cropperWrapper.el.style.left = `${offset.left}px`;\n        this.cropperWrapper.el.style.top = `${offset.top}px`;\n\n        await loadImage(this.originalSrc, cropperImage);\n        if (status(this) !== \"mounted\") {\n            return;\n        }\n\n        this.cropper = await activateCropper(\n            cropperImage,\n            cropperAspectRatios[this.aspectRatio]?.value || 0,\n            this.media.dataset\n        );\n\n        this.cropper.element.addEventListener(\"ready\", () => {\n            const cropperMove = this.cropperWrapper.el.querySelector(\".cropper-face.cropper-move\");\n            for (const shape of IMAGE_SHAPES) {\n                if (this.media.classList.contains(shape)) {\n                    cropperMove.classList.add(shape);\n                } else {\n                    cropperMove.classList.remove(shape);\n                }\n            }\n        });\n        this.isCropperActive = true;\n    }\n    /**\n     * Updates the DOM image with cropped data and associates required\n     * information for a potential future save (where required cropped data\n     * attachments will be created).\n     *\n     * @private\n     * @param {boolean} [cropped=true]\n     */\n    async save() {\n        const cropperData = this.getCropperData(this.cropper);\n        this.props.onSave?.({\n            aspectRatio: this.aspectRatio,\n            ...cropperData,\n        });\n        this.closeCropper();\n    }\n    /**\n     * Resets the crop box to prevent it going outside the image.\n     *\n     * @private\n     */\n    resetCropBox() {\n        this.cropper.clear();\n        this.cropper.crop();\n    }\n    /**\n     * Make sure the targeted image is in the visible viewport before crop.\n     *\n     * @private\n     */\n    async scrollToInvisibleImage() {\n        const rect = this.media.getBoundingClientRect();\n        const viewportTop = this.document.documentElement.scrollTop || 0;\n        const viewportBottom = viewportTop + window.innerHeight;\n        // Give priority to the closest scrollable element (e.g. for images in\n        // HTML fields, the element to scroll is different from the document's\n        // scrolling element).\n        const scrollable = closestScrollableY(this.media);\n\n        // The image must be in a position that allows access to it and its crop\n        // options buttons. Otherwise, the crop widget container can be scrolled\n        // to allow editing.\n        if (rect.top < viewportTop || viewportBottom - rect.bottom < 100) {\n            await scrollTo(this.media, {\n                behavior: \"smooth\",\n                ...(scrollable && { scrollable }),\n            });\n        }\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    onZoom(scale) {\n        this.cropper.zoom(scale);\n    }\n\n    onReset() {\n        this.cropper.reset();\n    }\n\n    onRotate(degree) {\n        this.cropper.rotate(degree);\n    }\n\n    onFlip(scaleDirection) {\n        const amount = this.cropper.getData()[scaleDirection] * -1;\n        this.cropper[scaleDirection](amount);\n    }\n\n    setAspectRatio(ratio) {\n        this.cropper.reset();\n        this.aspectRatio = ratio;\n        this.cropper.setAspectRatio(cropperAspectRatios[this.aspectRatio].value);\n    }\n\n    /**\n     * Discards crop if the user clicks outside of the widget.\n     *\n     * @private\n     * @param {MouseEvent} ev\n     */\n    onDocumentMousedown(ev) {\n        if (\n            this.props.document.body.contains(ev.target) &&\n            (this.elRef.el === ev.target || !this.elRef.el.contains(ev.target))\n        ) {\n            return this.closeCropper();\n        }\n    }\n    /**\n     * Save crop if user hits enter,\n     * discard crop on escape.\n     *\n     * @private\n     * @param {KeyboardEvent} ev\n     */\n    onDocumentKeydown(ev) {\n        if (ev.key === \"Enter\") {\n            return this.save();\n        } else if (ev.key === \"Escape\") {\n            ev.stopImmediatePropagation();\n            return this.closeCropper();\n        }\n    }\n    /**\n     * @param {Cropper} cropper\n     */\n    getCropperData(cropper) {\n        return Object.fromEntries(\n            cropperDataFieldsWithAspectRatio\n                .map((field) => [field, cropper.getData()[field]])\n                .filter(([, value]) => value)\n        );\n    }\n    /**\n     * Resets the cropbox on zoom to prevent crop box overflowing.\n     *\n     * @private\n     */\n    async onCropZoom() {\n        // Wait for the zoom event to be fully processed before reseting.\n        await new Promise((res) => setTimeout(res, 0));\n        this.resetCropBox();\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { Plugin } from \"../../plugin\";\nimport { ImageCrop } from \"./image_crop\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\n/**\n * @typedef { Object } ImageCropShared\n * @property { ImageCropPlugin['openCropImage'] } openCropImage\n */\n\nexport class ImageCropPlugin extends Plugin {\n    static id = \"imageCrop\";\n    static dependencies = [\"selection\", \"history\", \"imagePostProcess\"];\n    static shared = [\"openCropImage\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"cropImage\",\n                run: this.openCropImage.bind(this),\n                description: _t(\"Crop image\"),\n                icon: \"fa-crop\",\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        toolbar_items: [\n            {\n                id: \"image_crop\",\n                commandId: \"cropImage\",\n                groupId: \"image_modifiers\",\n            },\n        ],\n    };\n\n    getTargetedImage() {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        return targetedNodes.find((node) => node.tagName === \"IMG\");\n    }\n\n    async openCropImage(targetedImg, imageCropProps = {}) {\n        targetedImg = targetedImg || this.getTargetedImage();\n        if (!targetedImg) {\n            return;\n        }\n        return registry.category(\"main_components\").add(\"ImageCropping\", {\n            Component: ImageCrop,\n            props: {\n                media: targetedImg,\n                onSave: async (newDataset) => {\n                    // todo: should use the mutex if there is one?\n                    const updateImageAttributes =\n                        await this.dependencies.imagePostProcess.processImage({\n                            img: targetedImg,\n                            newDataset,\n                        });\n                    updateImageAttributes();\n                    this.dependencies.history.addStep();\n                },\n                document: this.document,\n                ...imageCropProps,\n                onClose: () => {\n                    registry.category(\"main_components\").remove(\"ImageCropping\");\n                    imageCropProps.onClose?.();\n                },\n            },\n        });\n    }\n}\n", "import { Component, useEffect, useRef } from \"@odoo/owl\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\n\nexport class ImageDescription extends Component {\n    static components = { Dialog };\n    static props = {\n        ...toolbarButtonProps,\n        openImageDescriptionPopover: Function,\n    };\n    static template = \"html_editor.ImageDescription\";\n}\n\nexport class ImageDescriptionPopover extends Component {\n    static props = {\n        close: Function,\n        description: {\n            type: String,\n            optional: true,\n        },\n        onConfirm: Function,\n        tooltip: {\n            type: String,\n            optional: true,\n        },\n    };\n    static template = \"html_editor.ImageDescriptionPopover\";\n\n    setup() {\n        this.state = {\n            description: this.props.description,\n            tooltip: this.props.tooltip,\n        };\n        this.inputRef = useRef(\"description\");\n        useEffect(\n            (el) => el?.focus(),\n            () => [this.inputRef.el]\n        );\n        useHotkey(\"escape\", () => this.props.close());\n    }\n\n    onSave() {\n        this.props.onConfirm(this.state.description || \"\", this.state.tooltip || \"\");\n        this.props.close();\n    }\n}\n", "import { Plugin } from \"../../plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { isImageUrl } from \"@html_editor/utils/url\";\nimport { ImageDescription, ImageDescriptionPopover } from \"./image_description\";\nimport { ImageToolbarDropdown } from \"./image_toolbar_dropdown\";\nimport { createFileViewer } from \"@web/core/file_viewer/file_viewer_hook\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { boundariesOut } from \"@html_editor/utils/position\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { ImageTransformButton } from \"./image_transform_button\";\nimport { callbacksForCursorUpdate } from \"@html_editor/utils/selection\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { fillEmpty } from \"@html_editor/utils/dom\";\nimport { reactive } from \"@odoo/owl\";\n\nfunction hasShape(imagePlugin, shapeName) {\n    return () => imagePlugin.isSelectionShaped(shapeName);\n}\n\nexport const IMAGE_SHAPES = [\"rounded\", \"rounded-circle\", \"shadow\", \"img-thumbnail\"];\n\nconst IMAGE_PADDING = [\n    { name: \"None\", value: 0 },\n    { name: \"Small\", value: 1 },\n    { name: \"Medium\", value: 2 },\n    { name: \"Large\", value: 3 },\n    { name: \"XL\", value: 5 },\n];\n\nconst IMAGE_SIZE = [\n    { name: \"Default\", value: \"\" },\n    { name: \"100%\", value: \"100%\" },\n    { name: \"50%\", value: \"50%\" },\n    { name: \"25%\", value: \"25%\" },\n];\n\n/**\n * @typedef { Object } ImageShared\n * @property { ImagePlugin['getTargetedImage'] } getTargetedImage\n * @property { ImagePlugin['previewImage'] } previewImage\n * @property { ImagePlugin['resetImageTransformation'] } resetImageTransformation\n */\n\n/**\n * @typedef {((img: HTMLImageElement) => void | true)[]} delete_image_overrides\n * @typedef {((img: HTMLImageElement) => boolean)[]} image_name_predicates\n */\n\nexport class ImagePlugin extends Plugin {\n    static id = \"image\";\n    static dependencies = [\"history\", \"dom\", \"selection\", \"overlay\"];\n    static shared = [\"getTargetedImage\", \"previewImage\", \"resetImageTransformation\"];\n    static defaultConfig = { allowImageTransform: true };\n    toolbarNamespace = \"image\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"deleteImage\",\n                description: _t(\"Remove (DELETE) image\"),\n                icon: \"fa-trash text-danger\",\n                run: this.deleteImage.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"previewImage\",\n                description: _t(\"Preview image\"),\n                icon: \"fa-search-plus\",\n                run: this.previewImage.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"setImageShapeRounded\",\n                description: _t(\"Set shape: Rounded\"),\n                icon: \"fa-square\",\n                run: () => this.setImageShape(\"rounded\", { excludeClasses: [\"rounded-circle\"] }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"setImageShapeCircle\",\n                description: _t(\"Set shape: Circle\"),\n                icon: \"fa-circle-o\",\n                run: () => this.setImageShape(\"rounded-circle\", { excludeClasses: [\"rounded\"] }),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"setImageShapeShadow\",\n                description: _t(\"Set shape: Shadow\"),\n                icon: \"fa-sun-o\",\n                run: () => this.setImageShape(\"shadow\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"setImageShapeThumbnail\",\n                description: _t(\"Set shape: Thumbnail\"),\n                icon: \"fa-picture-o\",\n                run: () => this.setImageShape(\"img-thumbnail\"),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"resizeImage\",\n                run: this.resizeImage.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        toolbar_namespace_providers: [\n            (targetedNodes) => {\n                if (\n                    targetedNodes.length &&\n                    targetedNodes.every((node) => node.nodeName === \"IMG\")\n                ) {\n                    return this.toolbarNamespace;\n                }\n            },\n        ],\n        toolbar_groups: [\n            withSequence(23, { id: \"image_preview\", namespaces: [\"image\"] }),\n            withSequence(24, { id: \"image_description\", namespaces: [\"image\"] }),\n            withSequence(25, { id: \"image_shape\", namespaces: [\"image\"] }),\n            withSequence(26, { id: \"image_padding\", namespaces: [\"image\"] }),\n            withSequence(26, { id: \"image_size\", namespaces: [\"image\"] }),\n            withSequence(26, { id: \"image_modifiers\", namespaces: [\"image\"] }),\n            withSequence(32, { id: \"image_delete\", namespaces: [\"image\"] }),\n        ],\n        toolbar_items: [\n            {\n                id: \"image_preview\",\n                groupId: \"image_preview\",\n                commandId: \"previewImage\",\n            },\n            {\n                id: \"image_description\",\n                description: _t(\"Edit media description\"),\n                groupId: \"image_description\",\n                Component: ImageDescription,\n                props: {\n                    openImageDescriptionPopover: this.openImageDescriptionPopover.bind(this),\n                },\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"shape_rounded\",\n                groupId: \"image_shape\",\n                commandId: \"setImageShapeRounded\",\n                isActive: hasShape(this, \"rounded\"),\n            },\n            {\n                id: \"shape_circle\",\n                groupId: \"image_shape\",\n                commandId: \"setImageShapeCircle\",\n                isActive: hasShape(this, \"rounded-circle\"),\n            },\n            {\n                id: \"shape_shadow\",\n                groupId: \"image_shape\",\n                commandId: \"setImageShapeShadow\",\n                isActive: hasShape(this, \"shadow\"),\n            },\n            {\n                id: \"shape_thumbnail\",\n                groupId: \"image_shape\",\n                commandId: \"setImageShapeThumbnail\",\n                isActive: hasShape(this, \"img-thumbnail\"),\n            },\n            {\n                id: \"image_padding\",\n                groupId: \"image_padding\",\n                description: _t(\"Set image padding\"),\n                Component: ImageToolbarDropdown,\n                props: {\n                    name: \"image_padding\",\n                    icon: \"html_editor.ImagePaddingIcon\",\n                    items: IMAGE_PADDING,\n                    onSelected: (item) => {\n                        this.setImagePadding({ size: item.value });\n                    },\n                },\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"image_size\",\n                groupId: \"image_size\",\n                description: _t(\"Resize image\"),\n                Component: ImageToolbarDropdown,\n                props: {\n                    name: \"image_size\",\n                    getDisplay: () => this.imageSize,\n                    items: IMAGE_SIZE,\n                    onSelected: (item) => {\n                        this.resizeImage({ size: item.value });\n                        this.updateImageParams();\n                    },\n                },\n                isAvailable: (selection) =>\n                    isHtmlContentSupported(selection) && (this.config.allowImageResize ?? true),\n            },\n            {\n                id: \"image_transform\",\n                groupId: \"image_modifiers\",\n                description: _t(\"Transform the picture (click twice to reset transformation)\"),\n                Component: ImageTransformButton,\n                props: this.getImageTransformProps(),\n                isAvailable: (selection) =>\n                    this.config.allowImageTransform && isHtmlContentSupported(selection),\n            },\n            {\n                id: \"image_delete\",\n                groupId: \"image_delete\",\n                commandId: \"deleteImage\",\n            },\n        ],\n\n        /** Handlers */\n        selectionchange_handlers: this.updateImageParams.bind(this),\n        post_undo_handlers: this.updateImageParams.bind(this),\n        post_redo_handlers: this.updateImageParams.bind(this),\n\n        /** Providers */\n        paste_media_url_command_providers: this.getCommandForImageUrlPaste.bind(this),\n    };\n\n    setup() {\n        this.imageSize = reactive({ displayName: \"Default\" });\n        this.addDomListener(this.editable, \"pointerup\", (e) => {\n            if (e.target.tagName === \"IMG\") {\n                const [anchorNode, anchorOffset, focusNode, focusOffset] = boundariesOut(e.target);\n                this.dependencies.selection.setSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                });\n                this.dependencies.selection.focusEditable();\n            }\n        });\n        this.fileViewer = createFileViewer();\n        this.overlay = this.dependencies.overlay.createOverlay(ImageDescriptionPopover, {\n            className: \"popover\",\n        });\n    }\n\n    destroy() {\n        super.destroy();\n    }\n\n    get imageSizeName() {\n        const targetedImg = this.getTargetedImage();\n        if (!targetedImg) {\n            return \"Default\";\n        }\n        return targetedImg.style.width || \"Default\";\n    }\n\n    setImagePadding({ size } = {}) {\n        const targetedImg = this.getTargetedImage();\n        if (!targetedImg) {\n            return;\n        }\n        for (const classString of targetedImg.classList) {\n            if (classString.match(/^p-[0-9]$/)) {\n                targetedImg.classList.remove(classString);\n            }\n        }\n        targetedImg.classList.add(`p-${size}`);\n        this.dependencies.history.addStep();\n    }\n    resizeImage({ size } = {}) {\n        const targetedImg = this.getTargetedImage();\n        if (!targetedImg) {\n            return;\n        }\n        targetedImg.style.width = size || \"\";\n        this.dependencies.history.addStep();\n    }\n\n    setImageShape(className, { excludeClasses = [] } = {}) {\n        const targetedImg = this.getTargetedImage();\n        if (!targetedImg) {\n            return;\n        }\n        for (const classString of excludeClasses) {\n            if (targetedImg.classList.contains(classString)) {\n                targetedImg.classList.remove(classString);\n            }\n        }\n        targetedImg.classList.toggle(className);\n        this.dependencies.history.addStep();\n    }\n\n    previewImage() {\n        const targetedImg = this.getTargetedImage();\n        if (!targetedImg) {\n            return;\n        }\n        let imageName;\n        // Keep the result from the first predicate that returns something.\n        this.getResource(\"image_name_predicates\").find((p) => {\n            imageName = p(targetedImg);\n            return imageName;\n        });\n        const fileModel = {\n            isImage: true,\n            isViewable: true,\n            name: imageName || targetedImg.src,\n            defaultSource: targetedImg.src,\n            downloadUrl: targetedImg.src,\n        };\n        this.document.getSelection().collapseToEnd();\n        this.fileViewer.open(fileModel);\n    }\n\n    deleteImage() {\n        const targetedImg = this.getTargetedImage();\n        if (targetedImg) {\n            if (this.delegateTo(\"delete_image_overrides\", targetedImg)) {\n                return;\n            }\n            const cursors = this.dependencies.selection.preserveSelection();\n            cursors.update(callbacksForCursorUpdate.remove(targetedImg));\n            const parentEl = closestBlock(targetedImg);\n            targetedImg.remove();\n            cursors.restore();\n            fillEmpty(parentEl);\n            this.dependencies.history.addStep();\n        }\n    }\n\n    getTargetedImage() {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        return targetedNodes.find((node) => node.tagName === \"IMG\");\n    }\n\n    hasImageSize(size) {\n        const targetedImg = this.getTargetedImage();\n        return targetedImg?.style?.width === size;\n    }\n\n    isSelectionShaped(shape) {\n        const targetedNodes = this.dependencies.selection\n            .getTargetedNodes()\n            .filter((n) => n.tagName === \"IMG\" && n.classList.contains(shape));\n        return targetedNodes.length > 0;\n    }\n\n    getImageAttribute(attributeName) {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const targetedImg = targetedNodes.find((node) => node.tagName === \"IMG\");\n        return targetedImg.getAttribute(attributeName) || undefined;\n    }\n\n    /**\n     * @param {string} url\n     */\n    getCommandForImageUrlPaste(url) {\n        if (isImageUrl(url)) {\n            return {\n                title: _t(\"Embed Image\"),\n                description: _t(\"Embed the image in the document.\"),\n                icon: \"fa-image\",\n                run: () => {\n                    const img = this.document.createElement(\"IMG\");\n                    img.setAttribute(\"src\", url);\n                    this.dependencies.dom.insert(img);\n                    this.dependencies.history.addStep();\n                },\n            };\n        }\n    }\n\n    updateImageDescription({ description, tooltip } = {}) {\n        const targetedImg = this.getTargetedImage();\n        if (!targetedImg) {\n            return;\n        }\n        targetedImg.setAttribute(\"alt\", description);\n        targetedImg.setAttribute(\"title\", tooltip);\n        this.dependencies.history.addStep();\n    }\n\n    resetImageTransformation(image) {\n        image.setAttribute(\n            \"style\",\n            (image.getAttribute(\"style\") || \"\").replace(/[^;]*transform[\\w:]*;?/g, \"\")\n        );\n        image.style.removeProperty(\"width\");\n        image.style.removeProperty(\"height\");\n        this.dependencies.history.addStep();\n    }\n\n    getImageTransformProps() {\n        return {\n            id: \"image_transform\",\n            icon: \"fa-object-ungroup\",\n            title: _t(\"Transform the picture (click twice to reset transformation)\"),\n            getTargetedImage: this.getTargetedImage.bind(this),\n            resetImageTransformation: this.resetImageTransformation.bind(this),\n            addStep: this.dependencies.history.addStep.bind(this),\n            document: this.document,\n            editable: this.editable,\n            activeTitle: _t(\"Click again to reset transformation\"),\n        };\n    }\n\n    updateImageParams() {\n        this.imageSize.displayName = this.imageSizeName;\n    }\n\n    openImageDescriptionPopover() {\n        const image = this.getTargetedImage();\n        if (image) {\n            this.overlay.open({\n                target: image,\n                props: {\n                    close: () => this.overlay.close(),\n                    description: this.getImageAttribute(\"alt\"),\n                    tooltip: this.getImageAttribute(\"title\"),\n                    onConfirm: (description, tooltip) => {\n                        this.updateImageDescription({ description, tooltip });\n                    },\n                },\n            });\n        }\n    }\n}\n", "import {\n    activateCropper,\n    getAspectRatio,\n    getDataURLBinarySize,\n    getImageSizeFromCache,\n    isGif,\n    isWebGLEnabled,\n    loadImage,\n    loadImageDataURL,\n    loadImageInfo,\n} from \"@html_editor/utils/image_processing\";\nimport { Plugin } from \"../../plugin\";\nimport { getAffineApproximation, getProjective } from \"@html_editor/utils/perspective_utils\";\n\nexport const DEFAULT_IMAGE_QUALITY = \"92\";\n\n/**\n * @typedef { Object } ImagePostProcessShared\n * @property { ImagePostProcessPlugin['processImage'] } processImage\n * @property { ImagePostProcessPlugin['getProcessedImageSize'] } getProcessedImageSize\n */\n\n/**\n * @typedef {(\n *   (img: HTMLImageElement, newDataset: object) => Promise<{\n *     getHeight: (canvas: HTMLCanvasElement) => number,\n *     perspective: string | null,\n *     newDataset: object,\n *     postProcessCroppedCanvas: (canvas: HTMLCanvasElement) => Promise<HTMLCanvasElement>,\n *     svg: SVGElement,\n *     svgAspectRatio: number,\n *     svgWidth: number,\n *   }>\n * )[]} process_image_warmup_handlers\n * @typedef {(\n *   (\n *     url: string,\n *     newDataset: object,\n *     processContext: { svg: SVGElement, svgAspectRatio: number, svgWidth: number }\n *   ) => Promise<[newUrl: string, handlerDataset: object]>\n * )[]} process_image_post_handlers\n * @typedef {((args: {imageEl: HTMLElement}) => void)[]} on_image_updated_handlers\n */\n\nexport class ImagePostProcessPlugin extends Plugin {\n    static id = \"imagePostProcess\";\n    static dependencies = [\"style\"];\n    static shared = [\"processImage\", \"getProcessedImageSize\"];\n\n    /**\n     * Applies data-attributes modifications to an img tag and returns a dataURL\n     * containing the result. This function does not modify the original image.\n     *\n     * @param {HTMLImageElement} img the image to which modifications are applied\n     * @param {Object} newDataset an object containing the modifications to apply\n     * @param {Function} [onImageInfoLoaded] can be used to fill\n     * newDataset after having access to image info, return true to cancel call\n     * @returns {{ url: string, newDataset: object }} Object containing the image\n     * URL and the updated dataset.\n     */\n    async _processImage({ img, newDataset = {}, onImageInfoLoaded }) {\n        const processContext = {};\n        if (!newDataset.originalSrc || !newDataset.mimetypeBeforeConversion) {\n            Object.assign(newDataset, await loadImageInfo(img));\n        }\n        if (onImageInfoLoaded) {\n            if (await onImageInfoLoaded(newDataset)) {\n                return;\n            }\n        }\n        for (const cb of this.getResource(\"process_image_warmup_handlers\")) {\n            const addedContext = await cb(img, newDataset);\n            if (addedContext) {\n                if (addedContext.newDataset) {\n                    Object.assign(newDataset, addedContext.newDataset);\n                }\n                Object.assign(processContext, addedContext);\n            }\n        }\n\n        const data = getImageTransformationData({ ...img.dataset, ...newDataset });\n        const {\n            mimetypeBeforeConversion,\n            formatMimetype,\n            width,\n            height,\n            resizeWidth,\n            filter,\n            glFilter,\n            filterOptions,\n            aspectRatio,\n            quality,\n        } = data;\n\n        const { postProcessCroppedCanvas, perspective, getHeight } = processContext;\n\n        // loadImage may have ended up loading a different src (see: LOAD_IMAGE_404)\n        const originalImg = await loadImage(data.originalSrc);\n        const originalSrc = originalImg.getAttribute(\"src\");\n\n        if (shouldPreventGifTransformation(data)) {\n            const [postUrl, postDataset] = await this.postProcessImage(\n                await loadImageDataURL(originalSrc),\n                newDataset,\n                processContext\n            );\n            return { url: postUrl, newDataset: postDataset };\n        }\n        // Crop\n        const container = document.createElement(\"div\");\n        container.appendChild(originalImg);\n        const cropper = await activateCropper(originalImg, aspectRatio, data);\n        const croppedCanvas = cropper.getCroppedCanvas(width, height);\n        cropper.destroy();\n        const processedCanvas = (await postProcessCroppedCanvas?.(croppedCanvas)) || croppedCanvas;\n\n        // Width\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = resizeWidth || processedCanvas.width;\n        canvas.height = getHeight\n            ? getHeight(canvas)\n            : (processedCanvas.height * canvas.width) / processedCanvas.width;\n        const ctx = canvas.getContext(\"2d\");\n        ctx.imageSmoothingQuality = \"high\";\n        ctx.mozImageSmoothingEnabled = true;\n        ctx.webkitImageSmoothingEnabled = true;\n        ctx.msImageSmoothingEnabled = true;\n        ctx.imageSmoothingEnabled = true;\n\n        // Perspective 3D\n        if (perspective) {\n            // x, y coordinates of the corners of the image as a percentage\n            // (relative to the width or height of the image) needed to apply\n            // the 3D effect.\n            const points = JSON.parse(perspective);\n            const divisions = 10;\n            const w = processedCanvas.width,\n                h = processedCanvas.height;\n\n            const project = getProjective(w, h, [\n                [(canvas.width / 100) * points[0][0], (canvas.height / 100) * points[0][1]], // Top-left [x, y]\n                [(canvas.width / 100) * points[1][0], (canvas.height / 100) * points[1][1]], // Top-right [x, y]\n                [(canvas.width / 100) * points[2][0], (canvas.height / 100) * points[2][1]], // bottom-right [x, y]\n                [(canvas.width / 100) * points[3][0], (canvas.height / 100) * points[3][1]], // bottom-left [x, y]\n            ]);\n\n            for (let i = 0; i < divisions; i++) {\n                for (let j = 0; j < divisions; j++) {\n                    const [dx, dy] = [w / divisions, h / divisions];\n\n                    const upper = {\n                        origin: [i * dx, j * dy],\n                        sides: [dx, dy],\n                        flange: 0.1,\n                        overlap: 0,\n                    };\n                    const lower = {\n                        origin: [i * dx + dx, j * dy + dy],\n                        sides: [-dx, -dy],\n                        flange: 0,\n                        overlap: 0.1,\n                    };\n\n                    for (const { origin, sides, flange, overlap } of [upper, lower]) {\n                        const [[a, c, e], [b, d, f]] = getAffineApproximation(project, [\n                            origin,\n                            [origin[0] + sides[0], origin[1]],\n                            [origin[0], origin[1] + sides[1]],\n                        ]);\n\n                        const ox = (i !== divisions ? overlap * sides[0] : 0) + flange * sides[0];\n                        const oy = (j !== divisions ? overlap * sides[1] : 0) + flange * sides[1];\n\n                        origin[0] += flange * sides[0];\n                        origin[1] += flange * sides[1];\n\n                        sides[0] -= flange * sides[0];\n                        sides[1] -= flange * sides[1];\n\n                        ctx.save();\n                        ctx.setTransform(a, b, c, d, e, f);\n\n                        ctx.beginPath();\n                        ctx.moveTo(origin[0] - ox, origin[1] - oy);\n                        ctx.lineTo(origin[0] + sides[0], origin[1] - oy);\n                        ctx.lineTo(origin[0] + sides[0], origin[1]);\n                        ctx.lineTo(origin[0], origin[1] + sides[1]);\n                        ctx.lineTo(origin[0] - ox, origin[1] + sides[1]);\n                        ctx.closePath();\n                        ctx.clip();\n                        ctx.drawImage(processedCanvas, 0, 0);\n\n                        ctx.restore();\n                    }\n                }\n            }\n        } else {\n            ctx.drawImage(\n                processedCanvas,\n                0,\n                0,\n                processedCanvas.width,\n                processedCanvas.height,\n                0,\n                0,\n                canvas.width,\n                canvas.height\n            );\n        }\n\n        // GL filter\n        const canUseWebGL = glFilter && isWebGLEnabled() && window.WebGLImageFilter;\n        if (canUseWebGL) {\n            const glf = new window.WebGLImageFilter();\n            const cv = document.createElement(\"canvas\");\n            cv.width = canvas.width;\n            cv.height = canvas.height;\n            applyAll = _applyAll.bind(null, canvas);\n            glFilters[glFilter](glf, cv, filterOptions);\n            const filtered = glf.apply(canvas);\n            ctx.drawImage(\n                filtered,\n                0,\n                0,\n                filtered.width,\n                filtered.height,\n                0,\n                0,\n                canvas.width,\n                canvas.height\n            );\n        }\n\n        // Color filter\n        ctx.fillStyle = filter || \"#0000\";\n        ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n        // Quality\n        newDataset.mimetype = formatMimetype || mimetypeBeforeConversion;\n        const dataURL = canvas.toDataURL(newDataset.mimetype, quality / 100);\n        const newSize = getDataURLBinarySize(dataURL);\n        const originalSize = getImageSizeFromCache(originalSrc);\n        const isChanged =\n            !!perspective ||\n            !!glFilter ||\n            originalImg.width !== canvas.width ||\n            originalImg.height !== canvas.height ||\n            originalImg.width !== processedCanvas.width ||\n            originalImg.height !== processedCanvas.height;\n\n        let url =\n            isChanged || originalSize >= newSize ? dataURL : await loadImageDataURL(originalSrc);\n        [url, newDataset] = await this.postProcessImage(url, newDataset, processContext);\n        return { url, newDataset };\n    }\n    async processImage(params) {\n        const processed = await this._processImage(params);\n        if (!processed) {\n            return () => {};\n        }\n        return () => this.updateImageAttributes(params.img, processed.url, processed.newDataset);\n    }\n    async getProcessedImageSize(img) {\n        const processed = await this._processImage({ img });\n        return getDataURLBinarySize(processed.url);\n    }\n    async postProcessImage(url, newDataset, processContext) {\n        for (const cb of this.getResource(\"process_image_post_handlers\")) {\n            const [newUrl, handlerDataset] = (await cb(url, newDataset, processContext)) || [];\n            url = newUrl || url;\n            newDataset = handlerDataset || newDataset;\n        }\n        return [url, newDataset];\n    }\n    updateImageAttributes(el, url, newDataset) {\n        el.classList.add(\"o_modified_image_to_save\");\n        if (el.tagName === \"IMG\") {\n            el.setAttribute(\"src\", url);\n        } else {\n            this.dependencies.style.setBackgroundImageUrl(el, url);\n        }\n        for (const key in newDataset) {\n            const value = newDataset[key];\n            if (value) {\n                el.dataset[key] = value;\n            } else {\n                delete el.dataset[key];\n            }\n        }\n        this.dispatchTo(\"on_image_updated_handlers\", { imageEl: el });\n    }\n}\n\nexport function getImageTransformationData(dataset) {\n    const data = Object.assign(\n        {\n            glFilter: \"\",\n            filter: \"#0000\",\n            forceModification: false,\n        },\n        dataset\n    );\n    for (const key of [\"width\", \"height\", \"resizeWidth\"]) {\n        data[key] = parseFloat(data[key]);\n    }\n    if (!(\"quality\" in data)) {\n        data.quality = DEFAULT_IMAGE_QUALITY;\n    }\n    // todo: this information could be inferred from x/y/width/height dataset\n    // properties.\n    data.aspectRatio = data.aspectRatio ? getAspectRatio(data.aspectRatio) : 0;\n    return data;\n}\n\nfunction shouldTransformImage(data) {\n    return (\n        data.perspective ||\n        data.glFilter ||\n        data.width ||\n        data.height ||\n        data.resizeWidth ||\n        data.aspectRatio\n    );\n}\n\nexport function shouldPreventGifTransformation(data) {\n    return isGif(data.mimetypeBeforeConversion) && !shouldTransformImage(data);\n}\n\nexport const defaultImageFilterOptions = {\n    blend: \"normal\",\n    filterColor: \"\",\n    blur: \"0\",\n    desaturateLuminance: \"0\",\n    saturation: \"0\",\n    contrast: \"0\",\n    brightness: \"0\",\n    sepia: \"0\",\n};\n\n// webgl color filters\nconst _applyAll = (result, filter, filters) => {\n    filters.forEach((f) => {\n        if (f[0] === \"blend\") {\n            const cv = f[1];\n            const ctx = result.getContext(\"2d\");\n            ctx.globalCompositeOperation = f[2];\n            ctx.globalAlpha = f[3];\n            ctx.drawImage(cv, 0, 0);\n            ctx.globalCompositeOperation = \"source-over\";\n            ctx.globalAlpha = 1.0;\n        } else {\n            filter.addFilter(...f);\n        }\n    });\n};\nlet applyAll;\n\nconst glFilters = {\n    blur: (filter) => filter.addFilter(\"blur\", 10),\n\n    1977: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        ctx.fillStyle = \"rgb(243, 106, 188)\";\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"screen\", 0.3],\n            [\"brightness\", 0.1],\n            [\"contrast\", 0.1],\n            [\"saturation\", 0.3],\n        ]);\n    },\n\n    aden: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        ctx.fillStyle = \"rgb(66, 10, 14)\";\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"darken\", 0.2],\n            [\"brightness\", 0.2],\n            [\"contrast\", -0.1],\n            [\"saturation\", -0.15],\n            [\"hue\", 20],\n        ]);\n    },\n\n    brannan: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        ctx.fillStyle = \"rgb(161, 44, 191)\";\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"lighten\", 0.31],\n            [\"sepia\", 0.5],\n            [\"contrast\", 0.4],\n        ]);\n    },\n\n    earlybird: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        const gradient = ctx.createRadialGradient(\n            cv.width / 2,\n            cv.height / 2,\n            0,\n            cv.width / 2,\n            cv.height / 2,\n            Math.hypot(cv.width, cv.height) / 2\n        );\n        gradient.addColorStop(0.2, \"#D0BA8E\");\n        gradient.addColorStop(1, \"#1D0210\");\n        ctx.fillStyle = gradient;\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"overlay\", 0.2],\n            [\"sepia\", 0.2],\n            [\"contrast\", -0.1],\n        ]);\n    },\n\n    inkwell: (filter, cv) => {\n        applyAll(filter, [\n            [\"sepia\", 0.3],\n            [\"brightness\", 0.1],\n            [\"contrast\", -0.1],\n            [\"desaturateLuminance\"],\n        ]);\n    },\n\n    // Needs hue blending mode for perfect reproduction. Close enough?\n    maven: (filter, cv) => {\n        applyAll(filter, [\n            [\"sepia\", 0.25],\n            [\"brightness\", -0.05],\n            [\"contrast\", -0.05],\n            [\"saturation\", 0.5],\n        ]);\n    },\n\n    toaster: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        const gradient = ctx.createRadialGradient(\n            cv.width / 2,\n            cv.height / 2,\n            0,\n            cv.width / 2,\n            cv.height / 2,\n            Math.hypot(cv.width, cv.height) / 2\n        );\n        gradient.addColorStop(0, \"#0F4E80\");\n        gradient.addColorStop(1, \"#3B003B\");\n        ctx.fillStyle = gradient;\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"screen\", 0.5],\n            [\"brightness\", -0.1],\n            [\"contrast\", 0.5],\n        ]);\n    },\n\n    walden: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        ctx.fillStyle = \"#CC4400\";\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"screen\", 0.3],\n            [\"sepia\", 0.3],\n            [\"brightness\", 0.1],\n            [\"saturation\", 0.6],\n            [\"hue\", 350],\n        ]);\n    },\n\n    valencia: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        ctx.fillStyle = \"#3A0339\";\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"exclusion\", 0.5],\n            [\"sepia\", 0.08],\n            [\"brightness\", 0.08],\n            [\"contrast\", 0.08],\n        ]);\n    },\n\n    xpro: (filter, cv) => {\n        const ctx = cv.getContext(\"2d\");\n        const gradient = ctx.createRadialGradient(\n            cv.width / 2,\n            cv.height / 2,\n            0,\n            cv.width / 2,\n            cv.height / 2,\n            Math.hypot(cv.width, cv.height) / 2\n        );\n        gradient.addColorStop(0.4, \"#E0E7E6\");\n        gradient.addColorStop(1, \"#2B2AA1\");\n        ctx.fillStyle = gradient;\n        ctx.fillRect(0, 0, cv.width, cv.height);\n        applyAll(filter, [\n            [\"blend\", cv, \"color-burn\", 0.7],\n            [\"sepia\", 0.3],\n        ]);\n    },\n\n    custom: (filter, cv, filterOptions) => {\n        const options = Object.assign(defaultImageFilterOptions, JSON.parse(filterOptions || \"{}\"));\n        const filters = [];\n        if (options.filterColor) {\n            const ctx = cv.getContext(\"2d\");\n            ctx.fillStyle = options.filterColor;\n            ctx.fillRect(0, 0, cv.width, cv.height);\n            filters.push([\"blend\", cv, options.blend, 1]);\n        }\n        delete options.blend;\n        delete options.filterColor;\n        filters.push(\n            ...Object.entries(options).map(([filter, amount]) => [filter, parseInt(amount) / 100])\n        );\n        applyAll(filter, filters);\n    },\n};\n", "import { Plugin } from \"@html_editor/plugin\";\nimport {\n    backgroundImageCssToParts,\n    backgroundImagePartsToCss,\n    getImageSrc,\n} from \"@html_editor/utils/image\";\nimport { rpc } from \"@web/core/network/rpc\";\n\n/**\n * @typedef { Object } ImageSaveShared\n * @property { ImageSavePlugin['savePendingImages'] } savePendingImages\n */\n\n/**\n * @typedef {((el: HTMLElement) => HTMLElement)[]} closest_savable_providers\n */\n\nexport class ImageSavePlugin extends Plugin {\n    static id = \"imageSave\";\n    static shared = [\"savePendingImages\"];\n\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        before_save_handlers: this.savePendingImages.bind(this),\n\n        ...(this.config.dropImageAsAttachment && {\n            added_image_handlers: (img) => img.classList.add(\"o_b64_image_to_save\"),\n        }),\n    };\n\n    async savePendingImages(editableEl = this.editable) {\n        // When saving a webp, o_b64_image_to_save is turned into\n        // o_modified_image_to_save by saveB64Image to request the saving\n        // of the pre-converted webp resizes and all the equivalent jpgs.\n        const getClosestSavable = (el) => {\n            for (const provider of this.getResource(\"closest_savable_providers\")) {\n                const value = provider(el);\n                if (value) {\n                    return value;\n                }\n            }\n        };\n        const oldSrcToNewSrcMap = new Map();\n        const b64Proms = [...editableEl.querySelectorAll(\".o_b64_image_to_save\")].map(\n            async (el) => {\n                const { resModel, resId } = this.getRecordInfo(getClosestSavable(el));\n                const oldSrc = el.getAttribute(\"src\");\n                await this.saveB64Image(el, resModel, resId);\n                oldSrcToNewSrcMap.set(oldSrc, el.getAttribute(\"src\"));\n            }\n        );\n        const modifiedProms = [...editableEl.querySelectorAll(\".o_modified_image_to_save\")].map(\n            async (el) => {\n                const { resModel, resId } = this.getRecordInfo(getClosestSavable(el));\n                const oldSrc = el.getAttribute(\"src\");\n                await this.saveModifiedImage(el, resModel, resId);\n                oldSrcToNewSrcMap.set(oldSrc, el.getAttribute(\"src\"));\n            }\n        );\n        const proms = [...b64Proms, ...modifiedProms];\n        const hasChange = !!proms.length;\n        if (hasChange) {\n            await Promise.all(proms);\n        }\n        return hasChange ? oldSrcToNewSrcMap : undefined;\n    }\n\n    createAttachment({ el, imageData, resModel, resId }) {\n        return rpc(\"/html_editor/attachment/add_data\", {\n            name: el.dataset.fileName || \"\",\n            data: imageData,\n            is_image: true,\n            res_model: resModel,\n            res_id: resId,\n        });\n    }\n\n    /**\n     * Saves a base64 encoded image as an attachment.\n     * Relies on saveModifiedImage being called after it for webp.\n     *\n     * @private\n     * @param {Element} el\n     * @param {string} resModel\n     * @param {number} resId\n     */\n    async saveB64Image(el, resModel, resId) {\n        const imageData = el.getAttribute(\"src\").split(\"base64,\")[1];\n        if (!imageData) {\n            // Checks if the image is in base64 format for RPC call. Relying\n            // only on the presence of the class \"o_b64_image_to_save\" is not\n            // robust enough.\n            el.classList.remove(\"o_b64_image_to_save\");\n            return;\n        }\n        const attachment = await this.createAttachment({\n            el,\n            imageData,\n            resId,\n            resModel,\n        });\n        if (!attachment) {\n            return;\n        }\n        if (attachment.mimetype === \"image/webp\") {\n            el.classList.add(\"o_modified_image_to_save\");\n            el.dataset.originalId = attachment.id;\n            el.dataset.mimetype = attachment.mimetype;\n            el.dataset.fileName = attachment.name;\n            return this.saveModifiedImage(el, resModel, resId);\n        } else {\n            let src = attachment.image_src;\n            if (!attachment.public) {\n                let accessToken = attachment.access_token;\n                if (!accessToken) {\n                    [accessToken] = await this.services.orm.call(\n                        \"ir.attachment\",\n                        \"generate_access_token\",\n                        [attachment.id]\n                    );\n                }\n                src += `?access_token=${encodeURIComponent(accessToken)}`;\n            }\n            el.setAttribute(\"src\", src);\n        }\n        el.classList.remove(\"o_b64_image_to_save\");\n    }\n\n    /**\n     * Saves a modified image as an attachment.\n     *\n     * @private\n     * @param {Element} el\n     * @param {string} resModel\n     * @param {number} resId\n     */\n    async saveModifiedImage(el, resModel, resId) {\n        const isBackground = !el.matches(\"img\");\n        // Modifying an image always creates a copy of the original, even if\n        // it was modified previously, as the other modified image may be used\n        // elsewhere if the snippet was duplicated or was saved as a custom one.\n        let altData = undefined;\n        const isImageField = !!el.closest(\"[data-oe-type=image]\");\n        if (el.dataset.mimetype === \"image/webp\" && isImageField) {\n            // Generate alternate sizes and format for reports.\n            altData = {};\n            const image = document.createElement(\"img\");\n            image.src = getImageSrc(el);\n            await new Promise((resolve) => image.addEventListener(\"load\", resolve));\n            const originalSize = Math.max(image.width, image.height);\n            const smallerSizes = [1024, 512, 256, 128].filter((size) => size < originalSize);\n            for (const size of [originalSize, ...smallerSizes]) {\n                const ratio = size / originalSize;\n                const canvas = document.createElement(\"canvas\");\n                canvas.width = image.width * ratio;\n                canvas.height = image.height * ratio;\n                const ctx = canvas.getContext(\"2d\");\n                ctx.fillStyle = \"rgb(255, 255, 255)\";\n                ctx.fillRect(0, 0, canvas.width, canvas.height);\n                ctx.drawImage(\n                    image,\n                    0,\n                    0,\n                    image.width,\n                    image.height,\n                    0,\n                    0,\n                    canvas.width,\n                    canvas.height\n                );\n                altData[size] = {\n                    \"image/jpeg\": canvas.toDataURL(\"image/jpeg\").split(\",\")[1],\n                };\n                if (size !== originalSize) {\n                    altData[size][\"image/webp\"] = canvas\n                        .toDataURL(\"image/webp\")\n                        .split(\",\")[1];\n                }\n            }\n        }\n        const newAttachmentSrc = await rpc(\n            `/html_editor/modify_image/${encodeURIComponent(el.dataset.originalId)}`,\n            {\n                res_model: resModel,\n                res_id: parseInt(resId),\n                data: getImageSrc(el).split(\",\")[1],\n                alt_data: altData,\n                mimetype: isBackground\n                    ? el.dataset.mimetype\n                    : el.getAttribute(\"src\").split(\":\")[1].split(\";\")[0],\n                name: el.dataset.fileName ? el.dataset.fileName : null,\n            }\n        );\n        el.classList.remove(\"o_modified_image_to_save\");\n        if (isBackground) {\n            const parts = backgroundImageCssToParts(el.style[\"background-image\"]);\n            parts.url = `url('${newAttachmentSrc}')`;\n            const combined = backgroundImagePartsToCss(parts);\n            el.style[\"background-image\"] = combined;\n        } else {\n            el.setAttribute(\"src\", newAttachmentSrc);\n        }\n        this.dispatchTo(\"on_image_saved_handlers\", { imageEl: el });\n    }\n\n    getRecordInfo(editableEl = null) {\n        return this.config.getRecordInfo ? this.config.getRecordInfo(editableEl) : {};\n    }\n}\n", "import { Component, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\n\nexport class ImageToolbarDropdown extends Component {\n    static components = { Dropdown, DropdownItem };\n    static props = {\n        ...toolbarButtonProps,\n        name: String,\n        icon: { type: String, optional: true },\n        onSelected: Function,\n        items: Array,\n        getDisplay: { type: Function, optional: true },\n    };\n    static template = \"html_editor.ImageToolbarDropdown\";\n\n    setup() {\n        this.items = this.props.items;\n        if (this.props.getDisplay) {\n            this.state = useState(this.props.getDisplay());\n        }\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n    }\n\n    onSelected(item) {\n        this.props.onSelected(item);\n    }\n}\n", "import { Component, useExternalListener, useState } from \"@odoo/owl\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { registry } from \"@web/core/registry\";\nimport { ImageTransformation } from \"./image_transformation\";\n\nexport function useImageTransform({ document, closeImageTransformation, buttonSelector }) {\n    let pointerDownInsideTransform = false;\n\n    // We close the image transform when we click outside any element not\n    // related to it. When the pointerdown of the click is inside the image\n    // transform and pointerup is outside while resizing or rotating the\n    // image it will consider the click as being done outside image transform.\n    // So we need to keep track if the pointerdown is inside or outside to know\n    // if we want to close the image transform component or not.\n    useExternalListener(document, \"pointerdown\", (ev) => {\n        if (isNodeInsideTransform(ev.target)) {\n            pointerDownInsideTransform = true;\n        } else {\n            closeImageTransformation();\n            pointerDownInsideTransform = false;\n        }\n    });\n    useExternalListener(\n        document,\n        \"click\",\n        (ev) => {\n            if (!isNodeInsideTransform(ev.target) && !pointerDownInsideTransform) {\n                closeImageTransformation();\n            }\n            pointerDownInsideTransform = false;\n        },\n        { capture: true }\n    );\n    // When we click on any character the image is deleted and we need to close\n    // the image transform. We handle this by selectionchange.\n    useExternalListener(document, \"selectionchange\", (ev) => {\n        closeImageTransformation();\n    });\n\n    function isNodeInsideTransform(node) {\n        if (!node) {\n            return false;\n        }\n        if (node.nodeType === Node.TEXT_NODE) {\n            node = node.parentElement;\n        }\n        if (node.matches(buttonSelector)) {\n            return true;\n        }\n        if (isImageTransformationOpen() && node.matches(\".transfo-controls, .transfo-controls *\")) {\n            return true;\n        }\n        return false;\n    }\n\n    function isImageTransformationOpen() {\n        return registry.category(\"main_components\").contains(\"ImageTransformation\");\n    }\n\n    return { isImageTransformationOpen };\n}\n\nexport class ImageTransformButton extends Component {\n    static template = \"html_editor.ImageTransformButton\";\n    static props = {\n        id: String,\n        icon: String,\n        title: String,\n        getTargetedImage: Function,\n        resetImageTransformation: Function,\n        addStep: Function,\n        document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },\n        editable: { validate: (p) => p.nodeType === Node.ELEMENT_NODE },\n        ...toolbarButtonProps,\n        activeTitle: String,\n    };\n\n    setup() {\n        this.state = useState({ active: false });\n        this.transform = useImageTransform({\n            document: this.props.document,\n            closeImageTransformation: this.closeImageTransformation.bind(this),\n            buttonSelector: '[name=\"image_transform\"], [name=\"image_transform\"] *',\n        });\n    }\n\n    onButtonClick() {\n        this.handleImageTransformation(this.props.getTargetedImage());\n    }\n\n    handleImageTransformation(image) {\n        if (this.transform.isImageTransformationOpen()) {\n            this.props.resetImageTransformation(image);\n            this.closeImageTransformation();\n        } else {\n            this.openImageTransformation(image);\n        }\n    }\n\n    openImageTransformation(image) {\n        this.state.active = true;\n        registry.category(\"main_components\").add(\"ImageTransformation\", {\n            Component: ImageTransformation,\n            props: {\n                image,\n                document: this.props.document,\n                editable: this.props.editable,\n                destroy: () => this.closeImageTransformation(),\n                onChange: () => this.props.addStep(),\n            },\n        });\n    }\n\n    closeImageTransformation() {\n        this.state.active = false;\n        if (this.transform.isImageTransformationOpen()) {\n            registry.category(\"main_components\").remove(\"ImageTransformation\");\n        }\n    }\n}\n", "/*\nCopyright (c) 2014 Christophe Matthieu,\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n*/\n\nimport { Component, onMounted, useExternalListener, useRef } from \"@odoo/owl\";\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { usePositionHook } from \"@html_editor/position_hook\";\n\nconst rad = Math.PI / 180;\nconst MIN_IMAGE_SIZE = 20;\n\nexport class ImageTransformation extends Component {\n    static template = \"html_editor.ImageTransformation\";\n    static props = {\n        document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },\n        editable: { validate: (p) => p.nodeType === Node.ELEMENT_NODE },\n        image: { validate: (p) => p.tagName === \"IMG\" },\n        destroy: { type: Function },\n        onChange: { type: Function },\n        onApply: { type: Function, optional: true },\n        onComponentMounted: { type: Function, optional: true },\n    };\n    static defaultProps = {\n        onComponentMounted: () => {},\n    };\n\n    setup() {\n        this.isCurrentlyTransforming = false;\n        this.document = this.props.document;\n        this.image = this.props.image;\n        this.transfoContainer = useRef(\"transfoContainer\");\n        this.transfoControls = useRef(\"transfoControls\");\n        this.transfoCenter = useRef(\"transfoCenter\");\n        this.computeImageTransformations();\n        onMounted(() => {\n            this.positionTransfoContainer();\n            this.props.onComponentMounted();\n        });\n        useExternalListener(window, \"mousemove\", this.mouseMove);\n        useExternalListener(window, \"mouseup\", this.mouseUp);\n        if (this.document.defaultView.frameElement) {\n            const iframeWindow = this.document.defaultView;\n            useExternalListener(iframeWindow, \"mousemove\", this.mouseMove);\n            useExternalListener(iframeWindow, \"mouseup\", this.mouseUp);\n        }\n        // When a character key is pressed and the image gets deleted,\n        // close the image transform via selectionchange.\n        useExternalListener(this.document, \"selectionchange\", () => this.destroy());\n        // Backspace/Delete don\u2019t trigger selectionchange on image\n        // delete in Chrome, so we use keydown event.\n        useExternalListener(this.document, \"keydown\", (ev) => {\n            if ([\"Backspace\", \"Delete\"].includes(ev.key)) {\n                this.destroy();\n            }\n        });\n        useHotkey(\"escape\", () => this.destroy());\n        usePositionHook({ el: this.props.editable }, this.document, () => {\n            if (!this.isCurrentlyTransforming) {\n                this.resetHandlers();\n            }\n        });\n    }\n\n    destroy() {\n        this.props.onApply?.();\n        this.props.destroy();\n    }\n\n    mouseMove(ev) {\n        if (!this.transfo.active) {\n            return;\n        }\n        ev.preventDefault();\n        const settings = this.transfo.settings;\n        const center = this.transfo.active.center;\n        const cdx = center.left - ev.pageX;\n        const cdy = center.top - ev.pageY;\n        if (this.transfo.active.type == \"rotator\") {\n            let ang;\n            const dang = Math.atan(settings.width / settings.height) / rad;\n\n            if (cdy) {\n                ang = Math.atan(-cdx / cdy) / rad;\n            } else {\n                ang = 0;\n            }\n            if (ev.pageY >= center.top && ev.pageX >= center.left) {\n                ang += 180;\n            } else if (ev.pageY >= center.top && ev.pageX < center.left) {\n                ang += 180;\n            } else if (ev.pageY < center.top && ev.pageX < center.left) {\n                ang += 360;\n            }\n\n            ang -= dang;\n\n            if (!ev.ctrlKey) {\n                settings.angle =\n                    Math.round(ang / this.transfo.settings.rotationStep) *\n                    this.transfo.settings.rotationStep;\n            } else {\n                settings.angle = ang;\n            }\n\n            // reset position : don't move center\n            this.positionTransfoContainer();\n            const new_center = this.getOffset(this.transfoCenter.el);\n            const x = center.left - new_center.left;\n            const y = center.top - new_center.top;\n            const angle = ang * rad;\n            settings.translatex += x * Math.cos(angle) - y * Math.sin(-angle);\n            settings.translatey += -x * Math.sin(angle) + y * Math.cos(-angle);\n        } else if (this.transfo.active.type == \"position\") {\n            const angle = settings.angle * rad;\n            const x = ev.pageX - this.transfo.active.pageX;\n            const y = ev.pageY - this.transfo.active.pageY;\n            this.transfo.active.pageX = ev.pageX;\n            this.transfo.active.pageY = ev.pageY;\n            const dx = x * Math.cos(angle) - y * Math.sin(-angle);\n            const dy = -x * Math.sin(angle) + y * Math.cos(-angle);\n\n            settings.translatex += dx;\n            settings.translatey += dy;\n        } else if (this.transfo.active.type.length === 2) {\n            const width = this.transfo.active.width;\n            const height = this.transfo.active.height;\n            const deltaX = ev.pageX - this.transfo.active.pageX;\n            const deltaY = ev.pageY - this.transfo.active.pageY;\n\n            let newWidth = width;\n            let newHeight = height;\n\n            if (this.transfo.active.type.indexOf(\"t\") != -1) {\n                newHeight = height - deltaY;\n            }\n            if (this.transfo.active.type.indexOf(\"b\") != -1) {\n                newHeight = height + deltaY;\n            }\n            if (this.transfo.active.type.indexOf(\"l\") != -1) {\n                newWidth = width - deltaX;\n            }\n            if (this.transfo.active.type.indexOf(\"r\") != -1) {\n                newWidth = width + deltaX;\n            }\n\n            // Ensure minimum dimensions\n            if (newWidth < MIN_IMAGE_SIZE) {\n                newWidth = MIN_IMAGE_SIZE;\n            }\n            if (newHeight < MIN_IMAGE_SIZE) {\n                newHeight = MIN_IMAGE_SIZE;\n            }\n\n            if (\n                ev.shiftKey &&\n                (this.transfo.active.type === \"tl\" ||\n                    this.transfo.active.type === \"bl\" ||\n                    this.transfo.active.type === \"tr\" ||\n                    this.transfo.active.type === \"br\")\n            ) {\n                const aspectRatio = width / height;\n                if (Math.abs(deltaX) > Math.abs(deltaY)) {\n                    newHeight = newWidth / aspectRatio;\n                } else {\n                    newWidth = newHeight * aspectRatio;\n                }\n            }\n            this.image.style.width = newWidth + \"px\";\n            this.image.style.height = newHeight + \"px\";\n            settings.width = newWidth;\n            settings.height = newHeight;\n        }\n\n        settings.angle = Math.round(settings.angle);\n        settings.translatex = Math.round(settings.translatex);\n        settings.translatey = Math.round(settings.translatey);\n\n        // When rotating, the offset used for the rotation center must be stable.\n        // getOffset normally includes CSS transforms, which would move the\n        // transfoCenter on each call and cause flickering.\n        // Temporarily remove the transform to compute the correct static position.\n        const prevImageTransform = this.image.style.transform;\n        this.image.style.transform = \"\";\n        this.transfo.settings.pos = this.getOffset(this.image);\n        this.image.style.transform = prevImageTransform;\n\n        this.positionTransfoContainer();\n    }\n\n    mouseUp() {\n        this.isCurrentlyTransforming = false;\n        this.transfo.active = null;\n        this.props.onApply?.();\n        this.props.onChange();\n    }\n\n    mouseDown(ev) {\n        if (this.transfo.active) {\n            return;\n        }\n        this.isCurrentlyTransforming = true;\n        let type = \"position\";\n        const target = ev.target.closest(\"div\");\n\n        if (target.classList.contains(\"transfo-rotator\")) {\n            type = \"rotator\";\n        } else if (target.classList.contains(\"transfo-scaler-tl\")) {\n            type = \"tl\";\n        } else if (target.classList.contains(\"transfo-scaler-tr\")) {\n            type = \"tr\";\n        } else if (target.classList.contains(\"transfo-scaler-br\")) {\n            type = \"br\";\n        } else if (target.classList.contains(\"transfo-scaler-bl\")) {\n            type = \"bl\";\n        } else if (target.classList.contains(\"transfo-scaler-tc\")) {\n            type = \"tc\";\n        } else if (target.classList.contains(\"transfo-scaler-bc\")) {\n            type = \"bc\";\n        } else if (target.classList.contains(\"transfo-scaler-ml\")) {\n            type = \"ml\";\n        } else if (target.classList.contains(\"transfo-scaler-mr\")) {\n            type = \"mr\";\n        }\n\n        this.transfo.active = {\n            type: type,\n            pageX: ev.pageX,\n            pageY: ev.pageY,\n            width: parseFloat(getComputedStyle(this.image).width),\n            height: parseFloat(getComputedStyle(this.image).height),\n            center: this.getOffset(this.transfoCenter.el),\n        };\n    }\n\n    computeImageTransformations() {\n        this.transfo = {};\n        const transform = this.image.style.transform || \"\";\n\n        this.transfo.settings = {};\n\n        this.transfo.settings.angle =\n            transform.indexOf(\"rotate\") != -1\n                ? parseFloat(transform.match(/rotate\\(([^)]+)deg\\)/)[1])\n                : 0;\n\n        this.image.style.transform = \"\";\n\n        this.transfo.settings.pos = this.getOffset(this.image);\n        this.transfo.settings.width = parseFloat(getComputedStyle(this.image).width);\n        this.transfo.settings.height = parseFloat(getComputedStyle(this.image).height);\n\n        const translatex = transform.match(/translateX\\(([0-9.-]+)(%|px)\\)/);\n        const translatey = transform.match(/translateY\\(([0-9.-]+)(%|px)\\)/);\n        this.transfo.settings.translate = \"%\";\n\n        if (translatex && translatex[2] === \"%\") {\n            this.transfo.settings.translatexp = parseFloat(translatex[1]);\n            this.transfo.settings.translatex =\n                (this.transfo.settings.translatexp / 100) * this.transfo.settings.width;\n        } else {\n            this.transfo.settings.translatex = translatex ? parseFloat(translatex[1]) : 0;\n        }\n        if (translatey && translatey[2] === \"%\") {\n            this.transfo.settings.translateyp = parseFloat(translatey[1]);\n            this.transfo.settings.translatey =\n                (this.transfo.settings.translateyp / 100) * this.transfo.settings.height;\n        } else {\n            this.transfo.settings.translatey = translatey ? parseFloat(translatey[1]) : 0;\n        }\n\n        this.transfo.settings.css = window.getComputedStyle(this.image, null);\n        this.transfo.settings.rotationStep = 5;\n    }\n\n    positionTransfoContainer() {\n        const settings = this.transfo.settings;\n        const width = parseFloat(getComputedStyle(this.image).width);\n        const height = parseFloat(getComputedStyle(this.image).height);\n        settings.translatexp = Math.round((settings.translatex / width) * 1000) / 10;\n        settings.translateyp = Math.round((settings.translatey / height) * 1000) / 10;\n\n        this.setImageTransformation(this.image);\n\n        this.transfoContainer.el.style.position = \"absolute\";\n        this.transfoContainer.el.style.width = width + \"px\";\n        this.transfoContainer.el.style.height = height + \"px\";\n        this.transfoContainer.el.style.top = settings.pos.top + \"px\";\n        this.transfoContainer.el.style.left = settings.pos.left + \"px\";\n\n        const controls = this.transfoControls.el;\n\n        this.setImageTransformation(controls);\n        controls.style.width = width + \"px\";\n        controls.style.height = height + \"px\";\n        controls.style.cursor = \"move\";\n    }\n\n    setImageTransformation(element) {\n        let transform = \"\";\n        if (this.transfo.settings.angle !== 0) {\n            transform += \" rotate(\" + this.transfo.settings.angle + \"deg) \";\n        }\n        if (this.transfo.settings.translatex) {\n            transform +=\n                \" translateX(\" +\n                (this.transfo.settings.translate === \"%\"\n                    ? this.transfo.settings.translatexp + \"%\"\n                    : this.transfo.settings.translatex + \"px\") +\n                \") \";\n        }\n        if (this.transfo.settings.translatey) {\n            transform +=\n                \" translateY(\" +\n                (this.transfo.settings.translate === \"%\"\n                    ? this.transfo.settings.translateyp + \"%\"\n                    : this.transfo.settings.translatey + \"px\") +\n                \") \";\n        }\n        element.style.transform = transform;\n    }\n\n    getOffset(target) {\n        if (!target.getClientRects().length) {\n            return { top: 0, left: 0 };\n        } else {\n            const rect = target.getBoundingClientRect();\n            const frameElement = target.ownerDocument.defaultView.frameElement;\n            const offset = { top: 0, left: 0 };\n            if (frameElement) {\n                const frameRect = frameElement.getBoundingClientRect();\n                offset.left += frameRect.left;\n                offset.top += frameRect.top;\n            }\n            return {\n                top: rect.top + window.pageYOffset + offset.top,\n                left: rect.left + window.pageXOffset + offset.left,\n            };\n        }\n    }\n\n    resetHandlers() {\n        this.computeImageTransformations();\n        this.positionTransfoContainer();\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport {\n    ICON_SELECTOR,\n    MEDIA_SELECTOR,\n    EDITABLE_MEDIA_CLASS,\n    isIconElement,\n    isMediaElement,\n    isProtected,\n    isProtecting,\n    paragraphRelatedElementsSelector,\n    isContentEditable,\n} from \"@html_editor/utils/dom_info\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { MediaDialog, TABS } from \"./media_dialog/media_dialog\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { boundariesOut, rightPos } from \"@html_editor/utils/position\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { fuzzyLookup } from \"@web/core/utils/search\";\nimport { FORMATTABLE_TAGS } from \"@html_editor/utils/formatting\";\n\n/**\n * @typedef { Object } MediaShared\n * @property { MediaPlugin['openMediaDialog'] } openMediaDialog\n */\n\n/**\n * @typedef {((mediaEl: HTMLElement) => void)[]} after_save_media_dialog_handlers\n * @typedef {((arg: { newMediaEl: HTMLElement }) => void)[]} on_added_media_handlers\n * @typedef {((elements: HTMLElement[], params: { node: Node }) => Promise<void>)[]} on_media_dialog_saved_handlers\n * @typedef {((arg: { newMediaEl: HTMLElement }) => void)[]} on_replaced_media_handlers\n * @typedef {((args: {imageEl: HTMLElement}) => void)[]} on_image_saved_handlers\n *\n * @typedef {{\n *      id: \"DOCUMENTS\" | \"ICONS\" | \"IMAGES\" | \"VIDEOS\";\n *      title: import(\"plugins\").TranslatedString;\n *      Component: import(\"@odoo/owl\").Component;\n *      sequence: number;\n *  }[]} media_dialog_extra_tabs\n */\n\nexport class MediaPlugin extends Plugin {\n    static id = \"media\";\n    static dependencies = [\"selection\", \"history\", \"dom\", \"dialog\"];\n    static shared = [\"openMediaDialog\"];\n    static defaultConfig = {\n        allowImage: true,\n        allowMediaDocuments: true,\n    };\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"replaceImage\",\n                description: _t(\"Replace media\"),\n                icon: \"fa-exchange\",\n                run: this.replaceImage.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"insertMedia\",\n                title: _t(\"Media\"),\n                description: this.config.allowVideo\n                    ? _t(\"Insert image, icon or video\")\n                    : _t(\"Insert image or icon\"),\n                icon: \"fa-file-image-o\",\n                run: (params, context = {}) =>\n                    this.openMediaDialog({\n                        activeTab: this.getActiveDialogTab(context.searchTerm),\n                    }),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        toolbar_groups: withSequence(31, { id: \"replace_image\", namespaces: [\"image\"] }),\n        toolbar_items: [\n            {\n                id: \"replace_image\",\n                groupId: \"replace_image\",\n                commandId: \"replaceImage\",\n            },\n        ],\n        powerbox_categories: withSequence(40, { id: \"media\", name: _t(\"Media\") }),\n        ...(this.config.allowImage && {\n            powerbox_items: this.getInsertMediaPowerboxItem(),\n        }),\n        power_buttons: withSequence(1, { commandId: \"insertMedia\" }),\n        closest_savable_providers: withSequence(20, (el) => this.editable),\n\n        /** Handlers */\n        clean_for_save_handlers: ({ root }) => this.cleanForSave(root),\n        normalize_handlers: this.normalizeMedia.bind(this),\n        selectionchange_handlers: this.selectAroundIcon.bind(this),\n\n        unsplittable_node_predicates: isIconElement, // avoid merge\n        is_node_editable_predicates: this.isEditableMediaElement.bind(this),\n        clipboard_content_processors: this.clean.bind(this),\n        clipboard_text_processors: (text) => text.replace(/\\u200B/g, \"\"),\n        functional_empty_node_predicates: isMediaElement,\n\n        selectors_for_feff_providers: () =>\n            `:is(${paragraphRelatedElementsSelector}, ${FORMATTABLE_TAGS.join(\n                \", \"\n            )}, A) > :is(${ICON_SELECTOR})`,\n    };\n\n    setup() {\n        this.availableTabs = [\n            ...Object.values(TABS),\n            ...this.getResource(\"media_dialog_extra_tabs\"),\n        ];\n    }\n\n    getInsertMediaPowerboxItem() {\n        const self = this;\n        return {\n            categoryId: \"media\",\n            commandId: \"insertMedia\",\n            // Evaluation is deferred because this.availableTabs is only ready after setup.\n            get keywords() {\n                return self.availableTabs.map((tab) => tab.title);\n            },\n        };\n    }\n\n    getRecordInfo(editableEl = null) {\n        return this.config.getRecordInfo ? this.config.getRecordInfo(editableEl) : {};\n    }\n\n    isEditableMediaElement(node) {\n        if (\n            (isMediaElement(node) || node.nodeName === \"IMG\") &&\n            (node.classList.contains(EDITABLE_MEDIA_CLASS) || isContentEditable(node))\n        ) {\n            return true;\n        }\n    }\n\n    replaceImage() {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const node = targetedNodes.find((node) => node.tagName === \"IMG\");\n        if (node) {\n            this.openMediaDialog({ node });\n            this.dependencies.history.addStep();\n        }\n    }\n\n    normalizeMedia(node) {\n        const mediaElements = [...node.querySelectorAll(MEDIA_SELECTOR)];\n        if (node.matches(MEDIA_SELECTOR)) {\n            mediaElements.push(node);\n        }\n        for (const el of mediaElements) {\n            if (isProtected(el) || isProtecting(el)) {\n                continue;\n            }\n            el.setAttribute(\n                \"contenteditable\",\n                el.hasAttribute(\"contenteditable\") ? el.getAttribute(\"contenteditable\") : \"false\"\n            );\n            // Do not update the text if it's already OK to avoid recording a\n            // mutation on Firefox. (Chrome filters them out.)\n            if (isIconElement(el) && el.textContent !== \"\\u200B\") {\n                el.textContent = \"\\u200B\";\n            }\n        }\n    }\n\n    clean(root) {\n        for (const el of root.querySelectorAll(MEDIA_SELECTOR)) {\n            if (isIconElement(el)) {\n                el.textContent = \"\";\n            }\n        }\n    }\n\n    cleanForSave(root) {\n        for (const el of root.querySelectorAll(MEDIA_SELECTOR)) {\n            if (isIconElement(el)) {\n                el.textContent = \"\";\n            }\n            el.removeAttribute(\"contenteditable\");\n        }\n    }\n\n    async onSaveMediaDialog(element, { node }) {\n        if (!element) {\n            // @todo @phoenix to remove\n            throw new Error(\"Element is required: onSaveMediaDialog\");\n            // return;\n        }\n        if (node) {\n            const changedIcon = isIconElement(node) && isIconElement(element);\n            if (changedIcon) {\n                // Preserve tag name when changing an icon and not recreate the\n                // editors unnecessarily.\n                for (const attribute of element.attributes) {\n                    node.setAttribute(attribute.nodeName, attribute.nodeValue);\n                }\n                element = node;\n            } else {\n                node.replaceWith(element);\n            }\n            this.dispatchTo(\"on_replaced_media_handlers\", { newMediaEl: element });\n        } else {\n            this.dependencies.dom.insert(element);\n            this.dispatchTo(\"on_added_media_handlers\", { newMediaEl: element });\n        }\n        // Collapse selection after the inserted/replaced element.\n        const [anchorNode, anchorOffset] = rightPos(element);\n        this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n        this.dispatchTo(\"after_save_media_dialog_handlers\", element);\n        this.dependencies.history.addStep();\n    }\n\n    openMediaDialog(params = {}, editableEl = null) {\n        const oldSave =\n            params.save || ((element) => this.onSaveMediaDialog(element, { node: params.node }));\n        params.save = async (...args) => {\n            const selection = args[0];\n            const elements = selection\n                ? selection[Symbol.iterator]\n                    ? selection\n                    : [selection]\n                : [];\n            for (const onMediaDialogSaved of this.getResource(\"on_media_dialog_saved_handlers\")) {\n                await onMediaDialogSaved(elements, { node: params.node });\n            }\n            return oldSave(...args);\n        };\n        const { resModel, resId, field, type } = this.getRecordInfo(editableEl);\n        const mediaDialogClosedPromise = this.dependencies.dialog.addDialog(MediaDialog, {\n            resModel,\n            resId,\n            useMediaLibrary: !!(\n                field &&\n                ((resModel === \"ir.ui.view\" && field === \"arch\") || type === \"html\")\n            ), // @todo @phoenix: should be removed and moved to config.mediaModalParams\n            media: params.node,\n            onAttachmentChange: this.config.onAttachmentChange || (() => {}),\n            noImages: !this.config.allowImage,\n            extraTabs: this.getResource(\"media_dialog_extra_tabs\"),\n            ...this.config.mediaModalParams,\n            ...params,\n        });\n        return mediaDialogClosedPromise;\n    }\n\n    /**\n     * @param {import(\"@html_editor/core/selection_plugin\").SelectionData} param0\n     */\n    selectAroundIcon({ editableSelection }) {\n        if (!editableSelection.isCollapsed) {\n            return;\n        }\n        const iconEl = closestElement(editableSelection.anchorNode, isIconElement);\n        if (!iconEl) {\n            return;\n        }\n        const [anchorNode, anchorOffset, focusNode, focusOffset] = boundariesOut(iconEl);\n        const iconOuterBoundaries = { anchorNode, anchorOffset, focusNode, focusOffset };\n        this.dependencies.selection.setSelection(iconOuterBoundaries);\n    }\n\n    /**\n     * @param {string} searchTerm\n     * @returns {string|undefined}\n     */\n    getActiveDialogTab(searchTerm) {\n        if (!searchTerm) {\n            return undefined;\n        }\n        const matchedTabs = fuzzyLookup(searchTerm, this.availableTabs, (tab) => tab.title);\n        if (!matchedTabs.length) {\n            return undefined;\n        }\n        return matchedTabs[0].id;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { VideoSelector } from \"./media_dialog/video_selector\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport class VideoPlugin extends Plugin {\n    static id = \"video\";\n    static defaultConfig = {\n        allowVideo: true,\n    };\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        ...(this.config.allowVideo && {\n            media_dialog_extra_tabs: {\n                id: \"VIDEOS\",\n                title: _t(\"Videos\"),\n                Component: this.componentForMediaDialog,\n                sequence: 30,\n            },\n        }),\n    };\n\n    get componentForMediaDialog() {\n        return VideoSelector;\n    }\n}\n", "import { useNativeDraggable } from \"@html_editor/utils/drag_and_drop\";\nimport { childNodeIndex, endPos, leftPos, nodeSize, rightPos } from \"@html_editor/utils/position\";\nimport { xml } from \"@odoo/owl\";\nimport { Plugin } from \"../plugin\";\nimport { closestElement } from \"../utils/dom_traversal\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { getDeepestPosition, isContentEditable } from \"@html_editor/utils/dom_info\";\n\n/** @typedef {import(\"plugins\").CSSSelector} CSSSelector */\n\n/**\n * @typedef {CSSSelector[]} move_node_blacklist_selectors\n * @typedef {CSSSelector[]} move_node_whitelist_selectors\n * @typedef {((movableElement: HTMLElement) => void)[]} set_movable_element_handlers\n * @typedef {(() => void)[]} unset_movable_element_handlers\n */\n\nconst WIDGET_CONTAINER_WIDTH = 25;\nconst WIDGET_MOVE_SIZE = 20;\n\nconst ALLOWED_ELEMENTS = \"h1, h2, h3, p, hr, pre, blockquote, li\";\n\nexport class MoveNodePlugin extends Plugin {\n    static id = \"movenode\";\n    static dependencies = [\"baseContainer\", \"selection\", \"history\", \"position\", \"localOverlay\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        layout_geometry_change_handlers: () => {\n            if (this.currentMovableElement) {\n                this.setMovableElement(this.currentMovableElement);\n            }\n            this.updateHooks();\n        },\n    };\n\n    setup() {\n        this.intersectionObserver = new IntersectionObserver(\n            this.intersectionObserverCallback.bind(this),\n            {\n                root: document,\n            }\n        );\n        this.visibleMovableElements = new Set();\n\n        this.elementHookMap = new Map();\n\n        this.addDomListener(this.editable, \"mousemove\", this.onMousemove, true);\n        this.addDomListener(this.editable, \"touchmove\", this.onMousemove, true);\n        this.addDomListener(this.document, \"keydown\", this.onDocumentKeydown, true);\n        this.addDomListener(this.document, \"mousemove\", this.onDocumentMousemove, true);\n        this.addDomListener(this.document, \"touchmove\", this.onDocumentMousemove, true);\n\n        // This container help to add zone into which the mouse can activate the move widget.\n        this.widgetHookContainer = this.dependencies.localOverlay.makeLocalOverlay(\n            \"oe-widget-hooks-container\"\n        );\n        // This container contains the differents widgets.\n        this.widgetContainer =\n            this.dependencies.localOverlay.makeLocalOverlay(\"oe-widgets-container\");\n        // This container contains the jquery helper element.\n        this.dragHelperContainer = this.dependencies.localOverlay.makeLocalOverlay(\n            \"oe-movenode-helper-container\"\n        );\n        // This container contains drop zones. They are the zones that handle where the drop should happen.\n        this.dropzonesContainer =\n            this.dependencies.localOverlay.makeLocalOverlay(\"oe-dropzones-container\");\n        // This container contains drop hint. The final rectangle showed to the user.\n        this.dropzoneHintContainer = this.dependencies.localOverlay.makeLocalOverlay(\n            \"oe-dropzone-hint-container\"\n        );\n\n        // Uncomment line for debugging tranparent zones\n        // this.widgetHookContainer.classList.add(\"debug\");\n        // this.dropzonesContainer.classList.add(\"debug\");\n\n        this.scrollableElement = closestElement(this.editable.parentElement);\n        while (\n            this.scrollableElement &&\n            getComputedStyle(this.scrollableElement).overflowY !== \"auto\"\n        ) {\n            this.scrollableElement = this.scrollableElement.parentElement;\n        }\n        this.scrollableElement = this.scrollableElement || this.editable;\n\n        this.resetHooksNextMousemove = true;\n        this.mutationObserver = new MutationObserver(() => {\n            this.resetHooksNextMousemove = true;\n            this.removeMoveWidget();\n        });\n        this.mutationObserver.observe(this.editable, {\n            childList: true,\n            subtree: true,\n            characterData: true,\n            characterDataOldValue: true,\n        });\n    }\n    destroy() {\n        super.destroy();\n        this.intersectionObserver.disconnect();\n        this.mutationObserver.disconnect();\n        this.smoothScrollOnDrag && this.smoothScrollOnDrag.destroy();\n    }\n    intersectionObserverCallback(entries) {\n        for (const entry of entries) {\n            const element = entry.target;\n            if (entry.isIntersecting && element.isConnected) {\n                this.visibleMovableElements.add(element);\n                this.resetHooksNextMousemove = true;\n            } else {\n                this.visibleMovableElements.delete(element);\n                const hookElement = this.elementHookMap.get(element);\n                if (hookElement) {\n                    // If hookElement is undefined, it means that this callback\n                    // was called after a new element was inserted in the\n                    // editable, but before the next updateHooks. The hook will\n                    // be created when that happens.\n                    hookElement.style.display = `none`;\n                }\n            }\n        }\n    }\n    updateHooks() {\n        const editableStyles = getComputedStyle(this.editable);\n        this.editableRect = this.editable.getBoundingClientRect();\n        const paddingLeft = parseInt(editableStyles.paddingLeft, 10) || 0;\n        this.editableRect.x = this.editableRect.x + paddingLeft - (WIDGET_CONTAINER_WIDTH + 5);\n        this.editableRect.width =\n            this.editableRect.width - paddingLeft + (WIDGET_CONTAINER_WIDTH + 5);\n        const containerRect = this.widgetHookContainer.getBoundingClientRect();\n        const elements = this.getMovableElements();\n\n        const elementsToGarbageCollect = new Set(this.elementHookMap.keys());\n        for (const index in elements) {\n            const element = elements[index];\n            elementsToGarbageCollect.delete(element);\n            let hookElement = this.elementHookMap.get(element);\n            if (!hookElement) {\n                hookElement = document.createElement(\"div\");\n                this.elementHookMap.set(element, hookElement);\n                hookElement.classList.add(\"oe-dropzone-hook\");\n                hookElement.addEventListener(\"mouseenter\", () => {\n                    if (element !== this.currentMovableElement) {\n                        this.setMovableElement(element);\n                    }\n                });\n                this.widgetHookContainer.append(hookElement);\n                hookElement.style.display = `none`;\n\n                this.intersectionObserver.observe(element);\n            }\n            hookElement.style.zIndex = index;\n        }\n        // For all the elements that are not in the dom, remove their\n        // corresponding hook.\n        for (const element of elementsToGarbageCollect) {\n            this.visibleMovableElements.delete(element);\n            this.elementHookMap.get(element).remove();\n            this.intersectionObserver.unobserve(element);\n            this.elementHookMap.delete(element);\n        }\n\n        const visibleElements = [...this.visibleMovableElements];\n        // Prevent layout thrashing by computing all the rects in advance.\n        const elementRects = visibleElements.map((element) => element.getBoundingClientRect());\n        for (const index in visibleElements) {\n            const element = visibleElements[index];\n            const elementRect = elementRects[index];\n            const hookElement = this.elementHookMap.get(element);\n\n            const style = getComputedStyle(element);\n            const marginTop = parseInt(style.marginTop, 10) || 0;\n            const marginBottom = parseInt(style.marginBottom, 10) || 0;\n            let hookBox;\n            if (element.tagName === \"HR\") {\n                hookBox = new DOMRect(\n                    elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH,\n                    elementRect.y - containerRect.top - marginTop,\n                    elementRect.width + WIDGET_CONTAINER_WIDTH,\n                    elementRect.height + marginTop + marginBottom\n                );\n            } else if (element.tagName === \"LI\") {\n                // For <li>, move hookBox to the left to avoid blocking\n                // checkboxes \u2014 needed for proper list item interaction.\n                hookBox = new DOMRect(\n                    elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH - WIDGET_MOVE_SIZE,\n                    elementRect.y - containerRect.top - marginTop,\n                    WIDGET_CONTAINER_WIDTH,\n                    elementRect.height + marginTop + marginBottom\n                );\n            } else {\n                hookBox = new DOMRect(\n                    elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH,\n                    elementRect.y - containerRect.top - marginTop,\n                    WIDGET_CONTAINER_WIDTH,\n                    elementRect.height + marginTop + marginBottom\n                );\n            }\n\n            hookElement.style.left = `${hookBox.x}px`;\n            hookElement.style.top = `${hookBox.y}px`;\n            hookElement.style.width = `${hookBox.width}px`;\n            hookElement.style.height = `${hookBox.height}px`;\n            hookElement.style.display = `block`;\n        }\n    }\n    _updateAnchorWidgets(newAnchorWidget) {\n        const movableElement =\n            newAnchorWidget &&\n            closestElement(\n                newAnchorWidget,\n                (node) =>\n                    this.isNodeMovable(node) &&\n                    node.matches(\n                        [\n                            ALLOWED_ELEMENTS,\n                            baseContainerGlobalSelector,\n                            ...this.getResource(\"move_node_whitelist_selectors\"),\n                        ].join(\", \")\n                    )\n            );\n\n        if (movableElement && movableElement !== this.currentMovableElement) {\n            this.setMovableElement(movableElement);\n        }\n    }\n    getMovableElements() {\n        const elems = [];\n        for (const el of this.editable.querySelectorAll(\n            [\n                ALLOWED_ELEMENTS,\n                baseContainerGlobalSelector,\n                ...this.getResource(\"move_node_whitelist_selectors\"),\n            ].join(\", \")\n        )) {\n            if (this.isNodeMovable(el)) {\n                elems.push(el);\n            }\n        }\n        return elems;\n    }\n    getDroppableElements(draggableNode) {\n        return this.getMovableElements().filter(\n            (node) => !closestElement(node.parentElement, (n) => n === draggableNode)\n        );\n    }\n    setMovableElement(movableElement) {\n        this.removeMoveWidget();\n        this.currentMovableElement = movableElement;\n        this.dispatchTo(\"set_movable_element_handlers\", movableElement);\n\n        const containerRect = this.widgetContainer.getBoundingClientRect();\n        const anchorBlockRect = this.currentMovableElement.getBoundingClientRect();\n        const anchorX =\n            this.currentMovableElement.tagName === \"LI\"\n                ? anchorBlockRect.x - WIDGET_MOVE_SIZE // Prevent overlap bullets.\n                : anchorBlockRect.x;\n        let anchorY = anchorBlockRect.y;\n        if (this.currentMovableElement.tagName.match(/H[1-6]/)) {\n            anchorY += (anchorBlockRect.height - WIDGET_MOVE_SIZE) / 2;\n        }\n\n        this.moveWidget = this.document.createElement(\"div\");\n        this.moveWidget.className = \"oe-sidewidget-move oi oi-draggable\";\n        this.widgetContainer.append(this.moveWidget);\n\n        let moveWidgetOffsetTop = 0;\n        if (movableElement.tagName === \"HR\") {\n            const style = getComputedStyle(movableElement);\n            moveWidgetOffsetTop = parseInt(style.marginTop, 10) || 0;\n        }\n\n        this.moveWidget.style.width = `${WIDGET_MOVE_SIZE}px`;\n        this.moveWidget.style.height = `${WIDGET_MOVE_SIZE}px`;\n        this.moveWidget.style.top = `${anchorY - containerRect.y - moveWidgetOffsetTop}px`;\n        this.moveWidget.style.left = `${anchorX - containerRect.x - WIDGET_CONTAINER_WIDTH}px`;\n\n        this.services.tooltip.add(this.moveWidget, {\n            template: xml`\n                <div class=\"o-tooltip tooltip-inner text-start px-3\">\n                    ${_t(\"Drag to move\")}<br/>\n                    ${_t(\"Click to select\")}\n                </div>`,\n            arrow: true,\n        });\n\n        this.addDomListener(this.moveWidget, \"click\", () => {\n            const isNodeContentEditable = isContentEditable(movableElement);\n            const [anchorNode, anchorOffset] = isNodeContentEditable\n                ? getDeepestPosition(movableElement, 0)\n                : leftPos(movableElement);\n            const [focusNode, focusOffset] = isNodeContentEditable\n                ? getDeepestPosition(movableElement, nodeSize(movableElement))\n                : rightPos(movableElement);\n            this.dependencies.selection.setSelection({\n                anchorNode,\n                anchorOffset,\n                focusNode,\n                focusOffset,\n            });\n            this.dependencies.selection.focusEditable();\n        });\n\n        if (this.scrollableElement) {\n            this.smoothScrollOnDrag && this.smoothScrollOnDrag.destroy();\n            // TODO: This should be made more generic, one hook for the entire\n            // editable with each element handled.\n            this.smoothScrollOnDrag = useNativeDraggable(simpleDraggableHook, {\n                ref: { el: this.widgetContainer },\n                elements: \".oe-sidewidget-move\",\n                onDragStart: () => this.startDropzones(movableElement, containerRect),\n                onDragEnd: () => this._stopDropzones(movableElement),\n                helper: () => {\n                    const container =\n                        movableElement.tagName === \"LI\"\n                            ? movableElement.parentElement.cloneNode(false)\n                            : document.createElement(\"div\");\n                    if (container.tagName === \"OL\") {\n                        const originalIndex = childNodeIndex(movableElement) + 1;\n                        container.setAttribute(\"start\", originalIndex);\n                    }\n                    container.append(movableElement.cloneNode(true));\n                    const style = getComputedStyle(movableElement);\n                    container.style.height = style.height;\n                    container.style.width = style.width;\n                    container.style.paddingLeft = \"25px\";\n                    container.style.opacity = \"0.4\";\n                    this.dragHelperContainer.append(container);\n                    return container;\n                },\n            });\n        }\n    }\n    removeMoveWidget() {\n        this.dispatchTo(\"unset_movable_element_handlers\");\n        this.moveWidget?.remove();\n        this.moveWidget = undefined;\n        this.currentMovableElement = undefined;\n    }\n    startDropzones(movableElement, containerRect, directions = [\"north\", \"south\"]) {\n        this.removeMoveWidget();\n        const elements = this.getDroppableElements(movableElement);\n\n        this.dropzonesContainer.replaceChildren();\n        this.editable.classList.add(\"oe-editor-dragging\");\n\n        for (const element of elements) {\n            const originalRect = element.getBoundingClientRect();\n            const style = getComputedStyle(element);\n            const marginTop = parseInt(style.marginTop, 10);\n            const marginBottom = parseInt(style.marginBottom, 10);\n            const marginLeft = parseInt(style.marginLeft, 10);\n            const marginRight = parseInt(style.marginRight, 10);\n\n            const dropzoneRect = new DOMRect(\n                originalRect.left - marginLeft - WIDGET_CONTAINER_WIDTH,\n                originalRect.top - marginTop,\n                originalRect.width + marginLeft + marginRight + WIDGET_CONTAINER_WIDTH,\n                originalRect.height + marginTop + marginBottom\n            );\n            const dropzoneHintRect = new DOMRect(\n                originalRect.left - marginLeft,\n                originalRect.top - marginTop,\n                originalRect.width + marginLeft + marginRight,\n                originalRect.height + marginTop + marginBottom\n            );\n\n            const dropzoneBox = document.createElement(\"div\");\n            dropzoneBox.className = `oe-dropzone-box`;\n            dropzoneBox.style.top = `${dropzoneRect.top - containerRect.top}px`;\n            dropzoneBox.style.left =\n                element.tagName == \"LI\"\n                    ? `${dropzoneRect.left - containerRect.left - WIDGET_MOVE_SIZE}px`\n                    : `${dropzoneRect.left - containerRect.left}px`;\n            dropzoneBox.style.width = `${dropzoneRect.width}px`;\n            dropzoneBox.style.height = `${dropzoneRect.height}px`;\n\n            const dropzoneHintBox = document.createElement(\"div\");\n            dropzoneHintBox.className = `oe-dropzone-box`;\n            dropzoneHintBox.style.top = `${dropzoneHintRect.top - containerRect.top}px`;\n            dropzoneHintBox.style.left = `${dropzoneHintRect.left - containerRect.left}px`;\n            dropzoneHintBox.style.width = `${dropzoneHintRect.width}px`;\n            dropzoneHintBox.style.height = `${dropzoneHintRect.height}px`;\n\n            const sideElements = {};\n            for (const direction of directions) {\n                const sideElement = document.createElement(\"div\");\n                sideElement.className = `oe-dropzone-box-side oe-dropzone-box-side-${direction}`;\n                sideElements[direction] = sideElement;\n                dropzoneBox.append(sideElement);\n                const onEnter = () => {\n                    this._currentZone = [direction];\n\n                    removeDropHint();\n                    this._currentDropHint = document.createElement(\"div\");\n                    this._currentDropHint.className = `oe-current-drop-hint`;\n                    const currentDropHintSize = 4;\n                    const currentDropHintSizeHalf = currentDropHintSize / 2;\n\n                    if (direction === \"north\") {\n                        this._currentDropHint.style[\"top\"] = `-${currentDropHintSizeHalf}px`;\n                        this._currentDropHint.style[\"width\"] = `100%`;\n                        this._currentDropHint.style[\"height\"] = `${currentDropHintSize}px`;\n                        dropzoneHintBox.append(this._currentDropHint);\n                        this._currentDropHintElementPosition = [\"top\", element];\n                    } else if (direction === \"south\") {\n                        this._currentDropHint.style[\"bottom\"] = `-${currentDropHintSizeHalf}px`;\n                        this._currentDropHint.style[\"width\"] = `100%`;\n                        this._currentDropHint.style[\"height\"] = `${currentDropHintSize}px`;\n                        dropzoneHintBox.append(this._currentDropHint);\n                        this._currentDropHintElementPosition = [\"bottom\", element];\n                    } else if (direction === \"west\") {\n                        this._currentDropHint.style[\"left\"] = `-${currentDropHintSizeHalf}px`;\n                        this._currentDropHint.style[\"height\"] = `100%`;\n                        this._currentDropHint.style[\"width\"] = `${currentDropHintSize}px`;\n                        dropzoneHintBox.append(this._currentDropHint);\n                        this._currentDropHintElementPosition = [\"left\", element];\n                    } else if (direction === \"east\") {\n                        this._currentDropHint.style[\"right\"] = `-${currentDropHintSizeHalf}px`;\n                        this._currentDropHint.style[\"height\"] = `100%`;\n                        this._currentDropHint.style[\"width\"] = `${currentDropHintSize}px`;\n                        dropzoneHintBox.append(this._currentDropHint);\n                        this._currentDropHintElementPosition = [\"right\", element];\n                    }\n                };\n                sideElement.addEventListener(\"mouseenter\", onEnter);\n                sideElement.addEventListener(\"pointerenter\", onEnter);\n                const removeDropHint = () => {\n                    if (this._currentDropHint) {\n                        this._currentDropHint.remove();\n                        this._currentDropHint = null;\n                    }\n                    this._currentDropHintElementPosition = null;\n                };\n                dropzoneBox.addEventListener(\"mouseleave\", removeDropHint);\n                dropzoneBox.addEventListener(\"pointerleave\", removeDropHint);\n            }\n\n            this.dropzonesContainer.append(dropzoneBox);\n            this.dropzoneHintContainer.append(dropzoneHintBox);\n        }\n    }\n    _stopDropzones(movableElement) {\n        this.editable.classList.remove(\"oe-editor-dragging\");\n        this.dropzonesContainer.replaceChildren();\n        this.dropzoneHintContainer.replaceChildren();\n\n        if (this._currentDropHintElementPosition) {\n            const [position, focusElelement] = this._currentDropHintElementPosition;\n            this._currentDropHintElementPosition = undefined;\n            const previousParent = movableElement.parentElement;\n\n            const isFocusInsideList = [\"UL\", \"OL\"].includes(focusElelement?.parentElement?.tagName);\n            if (movableElement.tagName === \"LI\" && !isFocusInsideList) {\n                // If LI is moved outside a list, wrap it in UL/OL (previous parent)\n                const wrapperList = previousParent.cloneNode(false);\n                wrapperList.appendChild(movableElement);\n                movableElement = wrapperList;\n            } else if (movableElement.tagName !== \"LI\" && isFocusInsideList) {\n                // If non-LI element is moved into a list, wrap it in a LI\n                const wrapperLI = this.document.createElement(\"LI\");\n                wrapperLI.appendChild(movableElement);\n                movableElement = wrapperLI;\n            }\n            if (position === \"top\") {\n                focusElelement.before(movableElement);\n            } else if (position === \"bottom\") {\n                focusElelement.after(movableElement);\n            }\n            if (previousParent.innerHTML.trim() === \"\") {\n                if ([\"UL\", \"OL\"].includes(previousParent.tagName)) {\n                    previousParent.remove();\n                } else {\n                    const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                    const br = document.createElement(\"br\");\n                    baseContainer.append(br);\n                    previousParent.append(baseContainer);\n                }\n            }\n            const selectionPosition = endPos(movableElement);\n            this.dependencies.selection.setSelection({\n                anchorNode: selectionPosition[0],\n                anchorOffset: selectionPosition[1],\n            });\n            this.dependencies.history.addStep();\n        }\n    }\n    onMousemove(e) {\n        this._updateAnchorWidgets(e.target);\n    }\n    onDocumentKeydown() {\n        // Hide the move widget upon keystroke for visual clarity and provide\n        // visibility to a collaborative avatar.\n        this.removeMoveWidget();\n    }\n    onDocumentMousemove(e) {\n        if (this.resetHooksNextMousemove) {\n            this.resetHooksNextMousemove = false;\n            this.removeMoveWidget();\n            this.updateHooks();\n        }\n        const clientX = e.clientX ?? e.touches?.[0]?.clientX;\n        const clientY = e.clientY ?? e.touches?.[0]?.clientY;\n        if (this.editableRect && !isPointInside(this.editableRect, clientX, clientY)) {\n            this.removeMoveWidget();\n        }\n    }\n    isNodeMovable(node) {\n        const blacklistSelectors = this.getResource(\"move_node_blacklist_selectors\").join(\", \");\n        if (blacklistSelectors && node.matches(blacklistSelectors)) {\n            return false;\n        }\n        return (\n            node.parentElement?.getAttribute(\"contentEditable\") === \"true\" ||\n            (node.tagName === \"LI\" && node.parentElement.isContentEditable)\n        );\n    }\n}\n\nfunction isPointInside(rect, x, y) {\n    return rect.left <= x && rect.right >= x && rect.top <= y && rect.bottom >= y;\n}\n\nconst simpleDraggableHook = {\n    acceptedParams: {\n        helper: [Function],\n    },\n    edgeScrolling: { enable: true },\n    onComputeParams({ ctx, params }) {\n        ctx.helper = params.helper;\n        ctx.followCursor = false;\n        ctx.tolerance = 0;\n    },\n    onDragStart({ ctx }) {\n        ctx.current.element = ctx.helper();\n        ctx.current.element.style.left = `${ctx.pointer.x + 10}px`;\n        ctx.current.element.style.top = `${ctx.pointer.y + 10}px`;\n        ctx.current.element.style.position = \"fixed\";\n        // makeDraggableHook disables pointer events, we want them in this case\n        document.body.classList.remove(\"pe-none\");\n        document.body.style.cursor = \"grabbing\";\n        return ctx.current;\n    },\n    onDrag({ ctx }) {\n        ctx.current.element.style.left = `${ctx.pointer.x}px`;\n        ctx.current.element.style.top = `${ctx.pointer.y}px`;\n    },\n    onDragEnd({ ctx }) {\n        ctx.current.element.remove();\n        if (document.body.style.cursor === \"grabbing\") {\n            document.body.style.cursor = \"\";\n        }\n        return ctx.current;\n    },\n};\n", "import { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { Plugin } from \"../plugin\";\nimport { childNodes } from \"@html_editor/utils/dom_traversal\";\nimport { isEmptyBlock } from \"@html_editor/utils/dom_info\";\nimport { withSequence } from \"@html_editor/utils/resource\";\n\nexport class PlaceholderPlugin extends Plugin {\n    static id = \"placeholder\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        ...(this.config.placeholder && {\n            hints: [\n                withSequence(1, {\n                    selector: `.odoo-editor-editable:not(:focus) > ${baseContainerGlobalSelector}:only-child`,\n                    text: this.config.placeholder,\n                }),\n            ],\n            hint_targets_providers: (selectionData, editable) => {\n                const el = editable.firstChild;\n                if (\n                    !selectionData.documentSelectionIsInEditable &&\n                    childNodes(editable).length === 1 &&\n                    isEmptyBlock(el) &&\n                    el.matches(baseContainerGlobalSelector)\n                ) {\n                    return [el];\n                } else {\n                    return [];\n                }\n            },\n        }),\n    };\n}\n", "import { ancestors } from \"@html_editor/utils/dom_traversal\";\nimport { Plugin } from \"../plugin\";\nimport { throttleForAnimation } from \"@web/core/utils/timing\";\nimport { couldBeScrollableX, couldBeScrollableY } from \"@web/core/utils/scrolling\";\n\n/**\n * @typedef {(() => void)[]} layout_geometry_change_handlers\n */\n/**\n * This plugin broadcasts layout/geometry changes to other plugins when\n * scrolling, resizing, or history changes occur.\n */\nexport class PositionPlugin extends Plugin {\n    static id = \"position\";\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        // todo: it is strange that the position plugin is aware of external_history_step_handlers and history_reset_from_steps_handlers.\n        external_history_step_handlers: this.layoutGeometryChange.bind(this),\n        history_reset_from_steps_handlers: this.layoutGeometryChange.bind(this),\n        step_added_handlers: this.layoutGeometryChange.bind(this),\n    };\n\n    setup() {\n        this.layoutGeometryChange = throttleForAnimation(this.layoutGeometryChange.bind(this));\n        this.resizeObserver = new ResizeObserver(this.layoutGeometryChange);\n        this.resizeObserver.observe(this.document.body);\n        this.resizeObserver.observe(this.editable);\n        this.addDomListener(window, \"resize\", this.layoutGeometryChange);\n        if (this.window !== window) {\n            this.addDomListener(this.window, \"resize\", this.layoutGeometryChange);\n        }\n        const scrollableElements = [this.editable, ...ancestors(this.editable)].filter(\n            (node) => couldBeScrollableX(node) || couldBeScrollableY(node)\n        );\n        for (const scrollableElement of scrollableElements) {\n            this.addDomListener(scrollableElement, \"scroll\", () => {\n                this.layoutGeometryChange();\n            });\n            this.resizeObserver.observe(scrollableElement);\n        }\n    }\n\n    destroy() {\n        this.resizeObserver.disconnect();\n        super.destroy();\n    }\n    layoutGeometryChange() {\n        this.dispatchTo(\"layout_geometry_change_handlers\");\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { isEditorTab, isEmptyBlock } from \"@html_editor/utils/dom_info\";\nimport { closestElement, descendants } from \"@html_editor/utils/dom_traversal\";\nimport { omit, pick } from \"@web/core/utils/objects\";\n\n/** @typedef {import(\"./powerbox/powerbox_plugin\").PowerboxCommand} PowerboxCommand */\n/** @typedef {import(\"@html_editor/core/selection_plugin\").EditorSelection} EditorSelection */\n\n/**\n * @typedef {Object} PowerButton\n * @property {string} commandId\n * @property {Object} [commandParams]\n * @property {string} [description] Can be inferred from the user command\n * @property {string} [icon] Can be inferred from the user command\n * @property {string} [text] Mandatory if `icon` is not provided\n * @property {string} [isAvailable] Can be inferred from the user command\n */\n\n/**\n * @typedef {((selection: EditorSelection) => boolean)[]} power_buttons_visibility_predicates\n */\n\n/**\n * @typedef {{ commandId: string }[]} power_buttons\n *\n * A power button is added by referencing an existing user command.\n *\n * Example:\n *\n *     resources = {\n *          user_commands: [\n *              {\n *                  id: myCommand,\n *                  run: myCommandFunction,\n *                  description: _t(\"Apply my command\"),\n *                  icon: \"fa-bug\",\n *              },\n *          ],\n *          power_buttons: [\n *              {\n *                  commandId: \"myCommand\",\n *                  commandParams: { myParam: \"myValue\" },\n *                  description: _t(\"Do powerfull stuff\"), // overrides the user command's `description`\n *                  // `icon` is derived from the user command\n *              }\n *          ],\n *     };\n */\n\nexport class PowerButtonsPlugin extends Plugin {\n    static id = \"powerButtons\";\n    static dependencies = [\n        \"baseContainer\",\n        \"selection\",\n        \"position\",\n        \"localOverlay\",\n        \"powerbox\",\n        \"userCommand\",\n        \"history\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        layout_geometry_change_handlers: this.updatePowerButtons.bind(this),\n        selectionchange_handlers: this.updatePowerButtons.bind(this),\n        post_mount_component_handlers: this.updatePowerButtons.bind(this),\n    };\n\n    setup() {\n        this.powerButtonsOverlay = this.dependencies.localOverlay.makeLocalOverlay(\n            \"oe-power-buttons-overlay\"\n        );\n        this.createPowerButtons();\n    }\n\n    createPowerButtons() {\n        const composePowerButton = (/**@type {PowerButton} */ item) => {\n            const command = this.dependencies.userCommand.getCommand(item.commandId);\n            return {\n                ...pick(command, \"description\", \"icon\"),\n                ...omit(item, \"commandId\", \"commandParams\"),\n                run: () => command.run(item.commandParams),\n                isAvailable: (selection) =>\n                    [command.isAvailable, item.isAvailable]\n                        .filter(Boolean)\n                        .every((predicate) => predicate(selection)),\n            };\n        };\n        const renderButton = ({ description, icon, text, run }) => {\n            const btn = this.document.createElement(\"button\");\n            let className = \"power_button btn px-2 py-1 cursor-pointer\";\n            if (icon) {\n                const iconLibrary = icon.includes(\"fa-\") ? \"fa\" : \"oi\";\n                className += ` ${iconLibrary} ${icon}`;\n            } else {\n                const span = this.document.createElement(\"span\");\n                span.textContent = text;\n                span.className = \"d-flex align-items-center text-nowrap\";\n                span.style.height = \"1em\";\n                btn.append(span);\n            }\n            btn.className = className;\n            btn.title = description;\n            this.addDomListener(btn, \"click\", () => this.applyCommand(run));\n            return btn;\n        };\n\n        /** @type {PowerButton[]} */\n        const powerButtonsDefinitions = this.getResource(\"power_buttons\");\n        // Merge properties from power_button and user_command.\n        const powerButtons = powerButtonsDefinitions.map(composePowerButton);\n        // Render HTML buttons.\n        this.descriptionToElementMap = new Map(powerButtons.map((pb) => [pb, renderButton(pb)]));\n\n        this.powerButtonsContainer = this.document.createElement(\"div\");\n        this.powerButtonsContainer.className = `o_we_power_buttons d-flex justify-content-center d-none`;\n        this.powerButtonsContainer.append(...this.descriptionToElementMap.values());\n        this.powerButtonsOverlay.append(this.powerButtonsContainer);\n    }\n\n    updatePowerButtons() {\n        this.powerButtonsContainer.classList.add(\"d-none\");\n        const { editableSelection, currentSelectionIsInEditable } =\n            this.dependencies.selection.getSelectionData();\n        if (!currentSelectionIsInEditable) {\n            return;\n        }\n        const block = closestBlock(editableSelection.anchorNode);\n        const element = closestElement(editableSelection.anchorNode);\n        const blockRect = block.getBoundingClientRect();\n        const editableRect = this.editable.getBoundingClientRect();\n        if (\n            editableSelection.isCollapsed &&\n            block?.matches(baseContainerGlobalSelector) &&\n            editableRect.bottom > blockRect.top &&\n            isEmptyBlock(block) &&\n            !descendants(block).some(isEditorTab) &&\n            !this.services.ui.isSmall &&\n            !closestElement(editableSelection.anchorNode, \"td, th, li\") &&\n            !block.style.textAlign &&\n            this.getResource(\"power_buttons_visibility_predicates\").every((predicate) =>\n                predicate(editableSelection)\n            )\n        ) {\n            this.powerButtonsContainer.classList.remove(\"d-none\");\n            const direction = closestElement(element, \"[dir]\")?.getAttribute(\"dir\");\n            this.powerButtonsContainer.setAttribute(\"dir\", direction);\n            // Hide/show buttons based on their availability.\n            for (const [{ isAvailable }, buttonElement] of this.descriptionToElementMap.entries()) {\n                const shouldHide = Boolean(!isAvailable(editableSelection));\n                buttonElement.classList.toggle(\"d-none\", shouldHide); // 2nd arg must be a boolean\n            }\n            this.setPowerButtonsPosition(block, blockRect, direction);\n        }\n    }\n\n    getPlaceholderWidth(block) {\n        let width;\n        this.dependencies.history.ignoreDOMMutations(() => {\n            const clone = block.cloneNode(true);\n            clone.innerText = clone.getAttribute(\"o-we-hint-text\");\n            clone.style.width = \"fit-content\";\n            clone.style.visibility = \"hidden\";\n            this.editable.appendChild(clone);\n            width = clone.getBoundingClientRect().width;\n            this.editable.removeChild(clone);\n        });\n        return width;\n    }\n\n    /**\n     *\n     * @param {HTMLElement} block\n     * @param {string} direction\n     */\n    setPowerButtonsPosition(block, blockRect, direction) {\n        const overlayStyles = this.powerButtonsOverlay.style;\n        // Resetting the position of the power buttons.\n        overlayStyles.top = \"0px\";\n        overlayStyles.left = \"0px\";\n        const buttonsRect = this.powerButtonsContainer.getBoundingClientRect();\n        const placeholderWidth = this.getPlaceholderWidth(block) + 20;\n        if (direction === \"rtl\") {\n            overlayStyles.left =\n                blockRect.right - buttonsRect.width - buttonsRect.x - placeholderWidth + \"px\";\n        } else {\n            overlayStyles.left = blockRect.left - buttonsRect.x + placeholderWidth + \"px\";\n        }\n        overlayStyles.top = blockRect.top - buttonsRect.top + \"px\";\n        overlayStyles.height = blockRect.height + \"px\";\n    }\n\n    /**\n     * @param {Function} commandFn\n     */\n    async applyCommand(commandFn) {\n        const btns = [...this.powerButtonsContainer.querySelectorAll(\".btn\")];\n        btns.forEach((btn) => btn.classList.add(\"disabled\"));\n        await commandFn();\n        btns.forEach((btn) => btn.classList.remove(\"disabled\"));\n    }\n}\n", "import { Component, onPatched, useEffect, useExternalListener, useRef } from \"@odoo/owl\";\n\n/**\n * @todo @phoenix i think that most of the \"control\" code in this component\n * should move to the powerbox plugin instead. This would probably be more robust\n */\nexport class Powerbox extends Component {\n    static template = \"html_editor.Powerbox\";\n    static props = {\n        document: { validate: (doc) => doc.constructor.name === \"HTMLDocument\" },\n        close: Function,\n        state: Object,\n        activateCommand: Function,\n        applyCommand: Function,\n    };\n\n    setup() {\n        const ref = useRef(\"root\");\n\n        onPatched(() => {\n            const activeCommand = ref.el.querySelector(\".o-we-command.active\");\n            if (activeCommand) {\n                activeCommand.scrollIntoView({ block: \"nearest\", inline: \"nearest\" });\n            }\n        });\n\n        this.mouseSelectionActive = false;\n        const onMouseMove = () => (this.mouseSelectionActive = true);\n        useExternalListener(this.props.document, \"mousemove\", onMouseMove);\n\n        // If necessary attach the same listener on the document on which\n        // the powerbox is mounted, serving the same purpose:\n        // do not trigger re-renderings when we are scrolling the powerbox\n        useEffect(\n            (ownDoc, propsDoc) => {\n                if (ownDoc && propsDoc && ownDoc !== propsDoc) {\n                    ownDoc.addEventListener(\"mousemove\", onMouseMove);\n                    return () => ownDoc.removeEventListener(\"mousemove\", onMouseMove);\n                }\n            },\n            () => [ref.el?.ownerDocument, this.props.document]\n        );\n    }\n\n    get commands() {\n        return this.props.state.commands;\n    }\n\n    get currentIndex() {\n        return this.props.state.currentIndex;\n    }\n\n    get showCategories() {\n        return this.props.state.showCategories;\n    }\n\n    onScroll() {\n        this.mouseSelectionActive = false;\n    }\n\n    onMouseEnter(index) {\n        if (this.mouseSelectionActive) {\n            this.props.activateCommand(index);\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { reactive } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rotate } from \"@web/core/utils/arrays\";\nimport { Powerbox } from \"./powerbox\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { omit, pick } from \"@web/core/utils/objects\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\n\n/** @typedef { import(\"@html_editor/core/selection_plugin\").EditorSelection } EditorSelection */\n/** @typedef { import(\"@html_editor/core/user_command_plugin\").UserCommand } UserCommand */\n/** @typedef { ReturnType<_t> } TranslatedString */\n\n/**\n * @typedef {Object} PowerboxCategory\n * @property {string} id\n * @property {TranslatedString} name\n *\n * @typedef {Object} PowerboxItem\n * @property {string} categoryId Id of a powerbox category\n * @property {string} commandId Id of a user command to extend\n * @property {Object} [commandParams] Passed to the command's `run` function - optional\n * @property {TranslatedString} [title] Inheritable\n * @property {TranslatedString} [description] Inheritable\n * @property {string} [icon] fa-class - Inheritable\n * @property {TranslatedString[]} [keywords]\n * @property {(selection: EditorSelection) => boolean} [isAvailable] Optional and inheritable\n */\n\n/**\n * The resulting powerbox command after deriving properties from a user command\n * (type for internal use).\n * @typedef {Object} PowerboxCommand\n * @property {string} categoryId\n * @property {string} categoryName\n * @property {string} title\n * @property {string} description\n * @property {string} icon\n * @property {Function} run\n * @property {TranslatedString[]} [keywords]\n * @property { (selection: EditorSelection) => boolean } isAvailable\n */\n\n/**\n * @typedef { Object } PowerboxShared\n * @property { PowerboxPlugin['closePowerbox'] } closePowerbox\n * @property { PowerboxPlugin['getAvailablePowerboxCommands'] } getAvailablePowerboxCommands\n * @property { PowerboxPlugin['openPowerbox'] } openPowerbox\n * @property { PowerboxPlugin['updatePowerbox'] } updatePowerbox\n */\n\n/** @typedef {PowerboxCategory[]} powerbox_categories */\n/**\n * @typedef {import(\"plugins\").CSSSelector[]} powerbox_blacklist_selectors\n *\n * @see UserCommand\n * @typedef {PowerboxItem[]} powerbox_items\n *\n * A powerbox item must derive from a user command (see UserCommand) specified\n * by commandId. Properties defined in a powerbox item override those from a\n * user command. Other properties are inferred from the UserCommand.\n *\n * Example:\n *\n *     resources = {\n *          user_commands: [\n *              // see {UserCommand}\n *              {\n *                  id: myCommand,\n *                  run: myCommandFunction,\n *                  title: _t(\"My Command\"),\n *                  description: _t(\"My command's description\"),\n *                  icon: \"fa-bug\",\n *              },\n *          ],\n *          powerbox_categories: [\n *              // see {PowerboxCategory}\n *              { id: \"myCategory\", name: _t(\"My Category\") }\n *          ],\n *          powerbox_items: [\n *              // see {PowerboxItem}\n *              {\n *                  categoryId: \"myCategory\",\n *                  commandId: \"myCommand\",\n *                  title: _t(\"My Powerbox Command\"), // overrides the user command's `title`\n *                  // `description` and `icon` are inferred from the user command\n *              }\n *          ],\n *     };\n */\n\nexport class PowerboxPlugin extends Plugin {\n    static id = \"powerbox\";\n    static dependencies = [\"overlay\", \"selection\", \"history\", \"userCommand\"];\n    static shared = [\n        \"closePowerbox\",\n        \"getAvailablePowerboxCommands\",\n        \"openPowerbox\",\n        \"updatePowerbox\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: {\n            id: \"openPowerbox\",\n            run: () =>\n                this.openPowerbox({\n                    commands: this.getAvailablePowerboxCommands(),\n                    categories: this.getResource(\"powerbox_categories\"),\n                }),\n        },\n        powerbox_categories: [\n            withSequence(10, { id: \"structure\", name: _t(\"Structure\") }),\n            withSequence(60, { id: \"widget\", name: _t(\"Widget\") }),\n            withSequence(100, { id: \"modules\", name: _t(\"Modules\") }),\n        ],\n        power_buttons: withSequence(100, {\n            commandId: \"openPowerbox\",\n            description: _t(\"More options\"),\n            icon: \"oi-ellipsis-v\",\n        }),\n        hints: withSequence(30, {\n            selector: baseContainerGlobalSelector,\n            text: _t('Type \"/\" for commands'),\n        }),\n    };\n\n    setup() {\n        /** @type {import(\"@html_editor/core/overlay_plugin\").Overlay} */\n        this.overlay = this.dependencies.overlay.createOverlay(Powerbox);\n\n        this.state = reactive({});\n        this.overlayProps = {\n            document: this.document,\n            close: () => this.overlay.close(),\n            state: this.state,\n            activateCommand: (currentIndex) => {\n                this.state.currentIndex = currentIndex;\n            },\n            applyCommand: this.applyCommand.bind(this),\n        };\n        this.powerboxCommands = this.makePowerboxCommands();\n        this.addDomListener(this.editable.ownerDocument, \"keydown\", this.onKeyDown);\n    }\n\n    /**\n     * @returns {PowerboxCommand[]}\n     */\n    getAvailablePowerboxCommands() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const blacklistSelector = this.getResource(\"powerbox_blacklist_selectors\").join(\", \");\n        if (blacklistSelector && closestElement(selection.anchorNode).matches(blacklistSelector)) {\n            return [];\n        }\n        return this.powerboxCommands.filter((cmd) => cmd.isAvailable(selection));\n    }\n\n    /**\n     * @returns {PowerboxCommand[]}\n     */\n    makePowerboxCommands() {\n        /** @type {PowerboxItem[]} */\n        const powerboxItems = this.getResource(\"powerbox_items\");\n        /** @type {PowerboxCategory[]} */\n        const categories = this.getResource(\"powerbox_categories\");\n        const categoryDict = Object.fromEntries(\n            categories.map((category) => [category.id, category])\n        );\n        return powerboxItems.map((/** @type {PowerboxItem} */ item) => {\n            const command = this.dependencies.userCommand.getCommand(item.commandId);\n            return {\n                ...pick(command, \"title\", \"description\", \"icon\"),\n                ...omit(item, \"commandId\", \"commandParams\"),\n                categoryName: categoryDict[item.categoryId].name,\n                run: (context) => command.run(item.commandParams, context),\n                isAvailable: (selection) =>\n                    [command.isAvailable, item.isAvailable]\n                        .filter(Boolean)\n                        .every((predicate) => predicate(selection)),\n            };\n        });\n    }\n\n    /**\n     * @param {Object} params\n     * @param {PowerboxCommand[]} params.commands\n     * @param {PowerboxCategory[]} [params.categories]\n     * @param {Function} [params.onApplyCommand=() => {}]\n     * @param {Function} [params.onClose=() => {}]\n     */\n    openPowerbox({ commands, categories, onApplyCommand = () => {}, onClose = () => {} } = {}) {\n        this.closePowerbox();\n        if (!commands.length) {\n            return;\n        }\n        this.onApplyCommand = onApplyCommand;\n        this.onClose = onClose;\n        this.updatePowerbox(commands, categories);\n    }\n\n    /**\n     * @param {PowerboxCommand[]} commands\n     * @param {PowerboxCategory[]} [categories]\n     */\n    updatePowerbox(commands, categories) {\n        if (categories) {\n            const orderCommands = [];\n            for (const category of categories) {\n                orderCommands.push(\n                    ...commands.filter((command) => command.categoryId === category.id)\n                );\n            }\n            commands = orderCommands;\n        }\n        Object.assign(this.state, {\n            showCategories: !!categories,\n            commands,\n            currentIndex: 0,\n        });\n        this.overlay.open({ props: this.overlayProps });\n    }\n\n    closePowerbox() {\n        if (!this.overlay.isOpen) {\n            return;\n        }\n        this.onClose();\n        this.overlay.close();\n    }\n\n    onKeyDown(ev) {\n        if (!this.overlay.isOpen) {\n            return;\n        }\n        const key = ev.key;\n        switch (key) {\n            case \"Escape\":\n                ev.stopImmediatePropagation();\n                this.closePowerbox();\n                break;\n            case \"Enter\":\n            case \"Tab\":\n                ev.preventDefault();\n                ev.stopImmediatePropagation();\n                this.applyCommand(this.state.commands[this.state.currentIndex]);\n                break;\n            case \"ArrowUp\": {\n                ev.preventDefault();\n                this.state.currentIndex = rotate(this.state.currentIndex, this.state.commands, -1);\n                break;\n            }\n            case \"ArrowDown\": {\n                ev.preventDefault();\n                this.state.currentIndex = rotate(this.state.currentIndex, this.state.commands, 1);\n                break;\n            }\n            case \"ArrowLeft\":\n            case \"ArrowRight\": {\n                this.closePowerbox();\n                break;\n            }\n        }\n    }\n\n    applyCommand(command) {\n        const context = {};\n        this.onApplyCommand(command, context);\n        command.run(context);\n        this.closePowerbox();\n    }\n}\n", "import { fuzzyLookup } from \"@web/core/utils/search\";\nimport { Plugin } from \"../../plugin\";\n\n/**\n * @typedef {import(\"./powerbox_plugin\").PowerboxCategory} CommandGroup\n * @typedef {import(\"../core/selection_plugin\").EditorSelection} EditorSelection\n */\n\nexport class SearchPowerboxPlugin extends Plugin {\n    static id = \"searchPowerbox\";\n    static dependencies = [\"powerbox\", \"selection\", \"history\", \"input\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        beforeinput_handlers: this.onBeforeInput.bind(this),\n        input_handlers: this.onInput.bind(this),\n        delete_handlers: this.update.bind(this),\n        post_undo_handlers: this.update.bind(this),\n        post_redo_handlers: this.update.bind(this),\n    };\n    setup() {\n        const categoryIds = new Set();\n        for (const category of this.getResource(\"powerbox_categories\")) {\n            if (categoryIds.has(category.id)) {\n                throw new Error(`Duplicate category id: ${category.id}`);\n            }\n            categoryIds.add(category.id);\n        }\n        this.categories = this.getResource(\"powerbox_categories\");\n        this.shouldUpdate = false;\n    }\n    onBeforeInput(ev) {\n        if (ev.data === \"/\") {\n            this.historySavePointRestore = this.dependencies.history.makeSavePoint();\n        }\n    }\n    onInput(ev) {\n        this.searchTerm = undefined;\n        if (ev.data === \"/\") {\n            this.openPowerbox();\n        } else {\n            this.update();\n        }\n    }\n    update() {\n        if (!this.shouldUpdate) {\n            return;\n        }\n        const selection = this.dependencies.selection.getEditableSelection();\n        this.searchNode = selection.startContainer;\n        if (!this.isSearching(selection)) {\n            this.dependencies.powerbox.closePowerbox();\n            return;\n        }\n        const searchTerm = this.searchNode.nodeValue.slice(this.offset + 1, selection.endOffset);\n        if (!searchTerm) {\n            this.dependencies.powerbox.updatePowerbox(this.enabledCommands, this.categories);\n            return;\n        }\n        if (searchTerm.includes(\" \")) {\n            this.dependencies.powerbox.closePowerbox();\n            return;\n        }\n        const commands = this.filterCommands(searchTerm);\n        if (!commands.length) {\n            this.dependencies.powerbox.closePowerbox();\n            this.shouldUpdate = true;\n            return;\n        }\n        this.searchTerm = searchTerm;\n        this.dependencies.powerbox.updatePowerbox(commands);\n    }\n    /**\n     * @param {string} searchTerm\n     */\n    filterCommands(searchTerm) {\n        return fuzzyLookup(searchTerm, this.enabledCommands, (cmd) => [\n            cmd.title,\n            cmd.categoryName,\n            cmd.description,\n            ...(cmd.keywords || []),\n        ]);\n    }\n    /**\n     * @param {EditorSelection} selection\n     */\n    isSearching(selection) {\n        return (\n            selection.endContainer === this.searchNode &&\n            this.searchNode.nodeValue &&\n            this.searchNode.nodeValue[this.offset] === \"/\" &&\n            selection.endOffset >= this.offset\n        );\n    }\n    openPowerbox() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        this.offset = selection.startOffset - 1;\n        this.enabledCommands = this.dependencies.powerbox.getAvailablePowerboxCommands();\n        this.dependencies.powerbox.openPowerbox({\n            commands: this.enabledCommands,\n            categories: this.categories,\n            onApplyCommand: (command, context) => {\n                context.searchTerm = this.searchTerm;\n                this.historySavePointRestore?.();\n            },\n            onClose: () => {\n                this.shouldUpdate = false;\n            },\n        });\n        this.shouldUpdate = true;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestBlock, isBlock } from \"@html_editor/utils/blocks\";\nimport { fillEmpty } from \"@html_editor/utils/dom\";\nimport {\n    allowsParagraphRelatedElements,\n    isEmpty,\n    isNotEditableNode,\n} from \"@html_editor/utils/dom_info\";\nimport {\n    closestElement,\n    getAdjacentNextSiblings,\n    getAdjacentPreviousSiblings,\n} from \"@html_editor/utils/dom_traversal\";\nimport { withSequence } from \"@html_editor/utils/resource\";\n\nconst BLINKER_CLASS = \"o-horizontal-caret\";\nconst PLACEHOLDER_ATTRIBUTE = \"data-selection-placeholder\";\nconst PLACEHOLDER_SELECTOR = `[${PLACEHOLDER_ATTRIBUTE}]`;\n\nexport class SelectionPlaceholderPlugin extends Plugin {\n    static id = \"selectionPlaceholder\";\n    static dependencies = [\"baseContainer\", \"history\", \"selection\"];\n    resources = {\n        external_history_step_handlers: this.updatePlaceholders.bind(this),\n        normalize_handlers: this.updatePlaceholders.bind(this),\n        step_added_handlers: this.updatePlaceholders.bind(this),\n        selectionchange_handlers: (selectionData) => this.onSelectionChange(selectionData),\n        clean_for_save_handlers: withSequence(0, ({ root }) => {\n            for (const placeholder of root.querySelectorAll(PLACEHOLDER_SELECTOR)) {\n                placeholder.remove();\n            }\n        }),\n        split_element_block_overrides: ({ blockToSplit }) => {\n            if (blockToSplit.hasAttribute(PLACEHOLDER_ATTRIBUTE)) {\n                this.persistPlaceholder(blockToSplit);\n                return true;\n            }\n        },\n        selection_blocker_predicates: (blocker) => {\n            if ((blocker.nodeType === Node.ELEMENT_NODE && blocker.hasAttribute(PLACEHOLDER_ATTRIBUTE)) || !isBlock(blocker)) {\n                return false;\n            } else if (isNotEditableNode(blocker)) {\n                return true;\n            }\n        },\n        selection_placeholder_container_predicates: (container) => {\n            if (!container.isContentEditable || !allowsParagraphRelatedElements(container)) {\n                return false;\n            } else if (container.getAttribute(\"contenteditable\") === \"true\") {\n                return true;\n            }\n        },\n        power_buttons_visibility_predicates: ({ anchorNode }) =>\n            !closestElement(anchorNode, PLACEHOLDER_SELECTOR),\n        move_node_blacklist_selectors: PLACEHOLDER_SELECTOR,\n        system_node_selectors: PLACEHOLDER_SELECTOR,\n        system_classes: BLINKER_CLASS,\n    };\n\n    setup() {\n        this.addDomListener(\n            this.editable,\n            \"focusout\",\n            () => this.editable.querySelectorAll(`.${BLINKER_CLASS}`).forEach(this.cleanBlinker),\n            { isGlobal: true }\n        );\n        this.addDomListener(this.editable, \"focusin\", () => this.resetBlinkerClasses(), {\n            isGlobal: true,\n        });\n    }\n\n    /**\n     * Update all placeholders and blinker classes so they are present\n     * everywhere we need them, and absent wherever they are not useful.\n     */\n    updatePlaceholders() {\n        const checkPredicate = (resourceId, node) => {\n            const results = this.getResource(resourceId)\n                .map((p) => p(node))\n                .filter((result) => result !== undefined);\n            return !!results.length && results.every(Boolean);\n        };\n        const isSelectionBlocker = (node) => checkPredicate(\"selection_blocker_predicates\", node);\n        const placeholderParents = [this.editable, ...this.editable.querySelectorAll(\"*\")].filter(\n            (container) => checkPredicate(\"selection_placeholder_container_predicates\", container)\n        );\n\n        // 1. Update current placeholders.\n        for (const placeholder of this.editable.querySelectorAll(PLACEHOLDER_SELECTOR)) {\n            const siblings = [\"before\", \"after\"].map((side) =>\n                getNonWhitespaceSibling(side, placeholder)\n            );\n            if (!isEmpty(placeholder) || !siblings.filter(Boolean).length) {\n                // Persist non-empty placeholders and any suddenly lonely placeholder.\n                this.persistPlaceholder(placeholder);\n            } else if (\n                !placeholderParents.includes(placeholder.parentElement) ||\n                !siblings.every((sibling) => !sibling || isSelectionBlocker(sibling))\n            ) {\n                // Remove illegitimate placeholders.\n                placeholder.remove();\n            } else {\n                // Update the margins.\n                this.applyMargin(placeholder, ...siblings);\n            }\n        }\n\n        // Get the blocks to check.\n        const blockers = [\n            ...new Set(placeholderParents.flatMap((element) => [...element.children])),\n        ].filter((element) => isSelectionBlocker(element));\n\n        // 2. Add placeholders before and after every blocker where necessary.\n        for (const blocker of blockers) {\n            for (const side of [\"before\", \"after\"]) {\n                // Get the first non-whitespace sibling.\n                const sibling = getNonWhitespaceSibling(side, blocker);\n                // Insert a placeholder if there is no such sibling or if it's a\n                // selection blocker.\n                if (!sibling || isSelectionBlocker(sibling)) {\n                    // Create the placeholder.\n                    const placeholder = this.dependencies.baseContainer.createBaseContainer();\n                    fillEmpty(placeholder);\n                    placeholder.setAttribute(PLACEHOLDER_ATTRIBUTE, \"\");\n                    // Position the placeholder.\n                    const siblings = side === \"before\" ? [sibling, blocker] : [blocker, sibling];\n                    this.applyMargin(placeholder, ...siblings);\n                    // Insert the placeholder.\n                    blocker[side](placeholder);\n                }\n            }\n        }\n        // 3. Reset blinker classes.\n        this.resetBlinkerClasses();\n    }\n\n    /**\n     * Position a placeholder between its siblings.\n     *\n     * @param {Element} placeholder\n     * @param {Element} previous\n     * @param {Element} next\n     */\n    applyMargin(placeholder, previous, next) {\n        const marginBefore = previous ? getMargin(previous, \"bottom\") : 0;\n        const marginAfter = next ? getMargin(next, \"top\") : 0;\n        const middleMargin = Math.abs(marginBefore - marginAfter) / 2;\n        if (middleMargin) {\n            const positiveMargin = Math.abs(\n                middleMargin - (marginAfter >= marginBefore ? marginAfter : marginBefore)\n            );\n            const negativeMargin = -1 - middleMargin;\n            const marginTop = marginAfter >= marginBefore ? positiveMargin : negativeMargin;\n            const marginBottom = marginAfter >= marginBefore ? negativeMargin : positiveMargin;\n            placeholder.style.margin = `${marginTop}px 0 ${marginBottom}px`;\n        }\n    }\n\n    /**\n     * Turn a selection placeholder into a real block.\n     *\n     * @param {Element} placeholder\n     */\n    persistPlaceholder(placeholder) {\n        placeholder.removeAttribute(PLACEHOLDER_ATTRIBUTE);\n        this.cleanBlinker(placeholder);\n        placeholder.removeAttribute(\"style\");\n    }\n\n    /**\n     * Remove the horizontal caret class from a placeholder element.\n     *\n     * @param {Element} blinker\n     */\n    cleanBlinker(blinker) {\n        if (blinker.className === BLINKER_CLASS) {\n            blinker.removeAttribute(\"class\");\n        } else {\n            blinker.classList.remove(BLINKER_CLASS);\n        }\n    }\n\n    /**\n     * Remove any irrelevant blinker class (horizontal caret) and make sure\n     * there is one on the placeholder in collapsed selection, if any.\n     *\n     * @param {import(\"@html_editor/core/selection_plugin\").EditorSelection} selection\n     */\n    resetBlinkerClasses(selection = this.dependencies.selection.getEditableSelection()) {\n        const anchorPlaceholder =\n            selection.isCollapsed && closestElement(selection.anchorNode, PLACEHOLDER_SELECTOR);\n        if (anchorPlaceholder && this.document.activeElement.contains(anchorPlaceholder)) {\n            anchorPlaceholder.classList.add(BLINKER_CLASS);\n        }\n        for (const blinker of this.editable.querySelectorAll(`.${BLINKER_CLASS}`)) {\n            if (blinker !== anchorPlaceholder || !this.document.activeElement.contains(blinker)) {\n                this.cleanBlinker(blinker);\n            }\n        }\n    }\n\n    /**\n     * Update the placeholders' states in function of the selection, by\n     * potentially persisting one, and by reseting the blinker classes.\n     *\n     * @param {import(\"@html_editor/core/selection_plugin\").SelectionData} selectionData\n     */\n    onSelectionChange(selectionData) {\n        const selection = selectionData.editableSelection;\n        this.resetBlinkerClasses(selection);\n        if (selection.isCollapsed) {\n            const anchor = closestElement(selection.anchorNode);\n            if (\n                closestBlock(anchor.parentElement) === this.editable &&\n                anchor?.hasAttribute(PLACEHOLDER_ATTRIBUTE) &&\n                !getNonWhitespaceSibling(\"next\", anchor)\n            ) {\n                // If it's at the bottom of the document, just persist immediately.\n                this.persistPlaceholder(anchor);\n                this.dependencies.history.addStep();\n            }\n        }\n    }\n}\n\n/**\n * @param {\"before\"|\"after\"} side\n * @param {Node} node\n * @returns {Node|undefined}\n */\nconst getNonWhitespaceSibling = (side, node) => {\n    const siblings =\n        side === \"before\" ? getAdjacentPreviousSiblings(node) : getAdjacentNextSiblings(node);\n    return siblings.find(\n        (sibling) => !(sibling.nodeType === Node.TEXT_NODE && !sibling.textContent.trim())\n    );\n};\n/**\n * Get an element's top or bottom margin as a number.\n *\n * @param {Element} element\n * @param {\"top\"|\"bottom\"} side\n * @returns {Number}\n */\nconst getMargin = (element, side) =>\n    +element.ownerDocument.defaultView\n        .getComputedStyle(element)\n        [side === \"top\" ? \"marginTop\" : \"marginBottom\"].replace(\"px\", \"\");\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Plugin } from \"../plugin\";\nimport { closestBlock } from \"../utils/blocks\";\nimport { closestElement, firstLeaf, selectElements } from \"../utils/dom_traversal\";\nimport {\n    isEmptyBlock,\n    isListItemElement,\n    paragraphRelatedElementsSelector,\n} from \"../utils/dom_info\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { removeClass } from \"@html_editor/utils/dom\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { fillEmpty } from \"../utils/dom\";\n\nexport class SeparatorPlugin extends Plugin {\n    static id = \"separator\";\n    static dependencies = [\"selection\", \"history\", \"split\", \"delete\", \"lineBreak\", \"baseContainer\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"insertSeparator\",\n                title: _t(\"Separator\"),\n                description: _t(\"Insert a horizontal rule separator\"),\n                icon: \"fa-minus\",\n                run: this.insertSeparator.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        powerbox_items: withSequence(1, {\n            categoryId: \"structure\",\n            commandId: \"insertSeparator\",\n        }),\n        content_not_editable_providers: (rootEl) => [...selectElements(rootEl, \"hr\")],\n        contenteditable_to_remove_selector: \"hr[contenteditable]\",\n        shorthands: [\n            {\n                pattern: /^---$/,\n                commandId: \"insertSeparator\",\n            },\n        ],\n\n        /** Handlers */\n        selectionchange_handlers: this.handleSelectionInHr.bind(this),\n        deselect_custom_selected_nodes_handlers: this.deselectHR.bind(this),\n        clean_handlers: this.deselectHR.bind(this),\n        clean_for_save_handlers: ({ root }) => {\n            this.deselectHR(root);\n        },\n    };\n\n    insertSeparator() {\n        const selection = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        const block = closestBlock(selection.startContainer);\n        const element =\n            closestElement(selection.startContainer, paragraphRelatedElementsSelector) ||\n            (block && !isListItemElement(block) ? block : null);\n\n        if (element && element !== this.editable) {\n            const sep = this.document.createElement(\"hr\");\n            const firstLeafNode = firstLeaf(block);\n            /**\n             * Insert the separator before the element when it\u2019s empty\n             * or when the caret is at the very start of the block.\n             */\n            if (\n                isEmptyBlock(element) ||\n                (selection.anchorNode === firstLeafNode && selection.anchorOffset === 0)\n            ) {\n                element.before(sep);\n            } else {\n                element.after(sep);\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                fillEmpty(baseContainer);\n                sep.after(baseContainer);\n                this.dependencies.selection.setCursorStart(baseContainer);\n            }\n        }\n        this.dependencies.history.addStep();\n    }\n\n    deselectHR(root = this.editable) {\n        for (const hr of root.querySelectorAll(\".o_selected_hr\")) {\n            removeClass(hr, \"o_selected_hr\");\n        }\n    }\n\n    handleSelectionInHr() {\n        this.deselectHR();\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        for (const node of targetedNodes) {\n            if (node.nodeName === \"HR\") {\n                node.classList.toggle(\"o_selected_hr\", true);\n            }\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { parseHTML } from \"@html_editor/utils/html\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport class StarPlugin extends Plugin {\n    static id = \"star\";\n    static dependencies = [\"dom\", \"history\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"addStars\",\n                title: _t(\"Stars\"),\n                description: _t(\"Insert a rating\"),\n                icon: \"fa-star\",\n                run: this.addStars.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        powerbox_items: [\n            {\n                title: _t(\"3 Stars\"),\n                description: _t(\"Insert a rating over 3 stars\"),\n                categoryId: \"widget\",\n                icon: \"fa-star-o\",\n                commandId: \"addStars\",\n                commandParams: { length: 3 },\n            },\n            {\n                title: _t(\"5 Stars\"),\n                description: _t(\"Insert a rating over 5 stars\"),\n                categoryId: \"widget\",\n                commandId: \"addStars\",\n                commandParams: { length: 5 },\n            },\n        ],\n    };\n\n    setup() {\n        this.addDomListener(this.editable, \"pointerdown\", this.onMouseDown);\n    }\n\n    onMouseDown(ev) {\n        const node = ev.target;\n        const isStar = (node) =>\n            node.nodeType === Node.ELEMENT_NODE &&\n            (node.classList.contains(\"fa-star\") || node.classList.contains(\"fa-star-o\"));\n        if (\n            isStar(node) &&\n            node.parentElement &&\n            node.parentElement.className.includes(\"o_stars\")\n        ) {\n            const allStars = Array.from(node.parentElement.childNodes).filter(isStar);\n            const currentStarIndex = allStars.indexOf(node);\n            const previousStars = allStars.slice(0, currentStarIndex);\n            const nextStars = allStars.slice(currentStarIndex + 1);\n            if (nextStars.length || previousStars.length) {\n                const shouldToggleOff =\n                    node.classList.contains(\"fa-star\") &&\n                    (!nextStars[0] || !nextStars[0].classList.contains(\"fa-star\"));\n                for (const star of [...previousStars, node]) {\n                    star.classList.toggle(\"fa-star-o\", shouldToggleOff);\n                    star.classList.toggle(\"fa-star\", !shouldToggleOff);\n                }\n                for (const star of nextStars) {\n                    star.classList.toggle(\"fa-star-o\", true);\n                    star.classList.toggle(\"fa-star\", false);\n                }\n                this.dependencies.history.addStep();\n            }\n            ev.stopPropagation();\n            ev.preventDefault();\n        }\n    }\n\n    addStars({ length }) {\n        const stars = Array.from({ length }, () => '<i class=\"fa fa-star-o\"></i>').join(\"\");\n        const html = `\\u200B<span contenteditable=\"false\" class=\"o_stars\">${stars}</span>\\u200B`;\n        this.dependencies.dom.insert(parseHTML(this.document, html));\n        this.dependencies.history.addStep();\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { reactive } from \"@odoo/owl\";\nimport { TableAlignSelector } from \"./table_align_selector\";\n\nconst verticalAlignmentItems = [\n    {\n        mode: \"top\",\n        template: \"html_editor.VerticalAlignTop\",\n    },\n    {\n        mode: \"middle\",\n        template: \"html_editor.VerticalAlignMiddle\",\n    },\n    {\n        mode: \"bottom\",\n        template: \"html_editor.VerticalAlignBottom\",\n    },\n];\n\nexport class TableAlignPlugin extends Plugin {\n    static id = \"tableAlign\";\n    static dependencies = [\"history\", \"selection\"];\n\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"alignTop\",\n                run: () => this.setVerticalAlignment(\"top\"),\n            },\n            {\n                id: \"alignMiddle\",\n                run: () => this.setVerticalAlignment(\"middle\"),\n            },\n            {\n                id: \"alignBottom\",\n                run: () => this.setVerticalAlignment(\"bottom\"),\n            },\n        ],\n        toolbar_items: [\n            {\n                id: \"table_alignment\",\n                groupId: \"layout\",\n                description: _t(\"Vertical align table cells content\"),\n                isAvailable: () =>\n                    this.dependencies.selection\n                        .getTargetedNodes()\n                        .some((node) => closestElement(node, \"td, th\")),\n                Component: TableAlignSelector,\n                props: {\n                    getItems: () => verticalAlignmentItems,\n                    getDisplay: () => this.verticalAlignMode,\n                    onSelected: (item) => {\n                        this.setVerticalAlignment(item.mode);\n                    },\n                },\n            },\n        ],\n\n        /** Handlers */\n        selectionchange_handlers: this.updateVerticalAlignParams.bind(this),\n        post_undo_handlers: this.updateVerticalAlignParams.bind(this),\n        post_redo_handlers: this.updateVerticalAlignParams.bind(this),\n        remove_all_formats_handlers: this.setVerticalAlignment.bind(this),\n\n        /** Predicates */\n        has_format_predicates: (node) => closestElement(node, \"td, th\")?.style.verticalAlign,\n    };\n\n    setup() {\n        this.verticalAlignMode = reactive({ displayName: \"\" });\n    }\n\n    get currentVerticalAlign() {\n        const targetedCells = this.dependencies.selection\n            .getTargetedNodes()\n            .map((node) => closestElement(node, \"td, th\"))\n            .filter(Boolean);\n\n        if (!targetedCells.length) {\n            return \"\";\n        }\n\n        const verticalAlign = targetedCells[0].style.verticalAlign;\n        return verticalAlign &&\n            targetedCells.every((cell) => cell.style.verticalAlign === verticalAlign)\n            ? verticalAlign\n            : \"\";\n    }\n\n    setVerticalAlignment(mode = \"\") {\n        const targetedCells = new Set(\n            this.dependencies.selection\n                .getTargetedNodes()\n                .map((node) => closestElement(node, \"td, th\"))\n                .filter(Boolean)\n        );\n        let isAlignmentUpdated = false;\n\n        for (const cell of targetedCells) {\n            if (cell.isContentEditable && cell.style.verticalAlign !== mode) {\n                cell.style.verticalAlign = mode;\n                isAlignmentUpdated = true;\n            }\n        }\n\n        if (isAlignmentUpdated) {\n            this.dependencies.history.addStep();\n        }\n        this.updateVerticalAlignParams();\n    }\n\n    updateVerticalAlignParams() {\n        this.verticalAlignMode.displayName = this.currentVerticalAlign;\n    }\n}\n", "import { Component, useState } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { toolbarButtonProps } from \"@html_editor/main/toolbar/toolbar\";\nimport { useDropdownAutoVisibility } from \"@html_editor/dropdown_autovisibility_hook\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\n\nexport class TableAlignSelector extends Component {\n    static template = \"html_editor.TableAlignSelector\";\n    static props = {\n        getItems: Function,\n        getDisplay: Function,\n        onSelected: Function,\n        ...toolbarButtonProps,\n    };\n    static components = { Dropdown };\n\n    setup() {\n        this.items = this.props.getItems();\n        this.state = useState(this.props.getDisplay());\n        this.menuRef = useChildRef();\n        useDropdownAutoVisibility(this.env.overlayState, this.menuRef);\n    }\n\n    onSelected(item) {\n        this.props.onSelected(item);\n    }\n}\n", "import { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { Component } from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport class TableMenu extends Component {\n    static template = \"html_editor.TableMenu\";\n    static props = {\n        type: String, // column or row\n        moveColumn: Function,\n        addColumn: Function,\n        removeColumn: Function,\n        moveRow: Function,\n        addRow: Function,\n        removeRow: Function,\n        turnIntoHeader: Function,\n        turnIntoRow: Function,\n        resetRowHeight: Function,\n        resetColumnWidth: Function,\n        resetTableSize: Function,\n        clearColumnContent: Function,\n        clearRowContent: Function,\n        overlay: Object,\n        dropdownState: Object,\n        target: { validate: (el) => el.nodeType === Node.ELEMENT_NODE },\n        direction: { type: String, optional: true },\n    };\n    static defaultProps = { direction: \"ltr\" };\n    static components = { Dropdown, DropdownItem };\n\n    setup() {\n        if (this.props.type === \"column\") {\n            this.isFirst = this.props.target.cellIndex === 0;\n            this.isLast = !this.props.target.nextElementSibling;\n        } else {\n            const tr = this.props.target.parentElement;\n            this.isFirst = !tr.previousElementSibling;\n            this.isLast = !tr.nextElementSibling;\n            this.isTableHeader = [...tr.children][0].nodeName === \"TH\";\n        }\n        this.items = this.props.type === \"column\" ? this.colItems() : this.rowItems();\n    }\n\n    get hasCustomTableSize() {\n        const table = closestElement(this.props.target, \"table\");\n        if (!table) {\n            return false;\n        }\n        const rows = [...table.rows];\n        const firstRowCells = [...rows[0].cells];\n        const rowHasHeight = rows.some((row) => row.style.height);\n        const cellHasWidth = firstRowCells.some((cell) => cell.style.width);\n        return rowHasHeight || cellHasWidth;\n    }\n\n    get hasCustomRowHeight() {\n        return !!this.props.target.closest(\"tr\").style.height;\n    }\n\n    get hasCustomColumnWidth() {\n        return (\n            !!this.props.target.closest(\"td\")?.style?.width ||\n            !!this.props.target.closest(\"th\")?.style?.width\n        );\n    }\n\n    onSelected(item) {\n        item.action(this.props.target);\n        this.props.overlay.close();\n    }\n\n    colItems() {\n        const ltr = this.props.direction === \"ltr\";\n        return [\n            !this.isFirst && {\n                name: \"move_left\",\n                icon: \"fa-chevron-left disabled\",\n                text: ltr ? _t(\"Move left\") : _t(\"Move right\"),\n                action: this.props.moveColumn.bind(this, \"left\"),\n            },\n            !this.isLast && {\n                name: \"move_right\",\n                icon: \"fa-chevron-right\",\n                text: ltr ? _t(\"Move right\") : _t(\"Move left\"),\n                action: this.props.moveColumn.bind(this, \"right\"),\n            },\n            {\n                name: \"insert_left\",\n                icon: \"fa-plus\",\n                text: ltr ? _t(\"Insert left\") : _t(\"Insert right\"),\n                action: this.props.addColumn.bind(this, \"before\"),\n            },\n            {\n                name: \"insert_right\",\n                icon: \"fa-plus\",\n                text: ltr ? _t(\"Insert right\") : _t(\"Insert left\"),\n                action: this.props.addColumn.bind(this, \"after\"),\n            },\n            {\n                name: \"delete\",\n                icon: \"fa-trash\",\n                text: _t(\"Delete\"),\n                action: this.props.removeColumn.bind(this),\n            },\n            this.hasCustomColumnWidth && {\n                name: \"reset_column_size\",\n                icon: \"fa-table\",\n                text: _t(\"Reset column size\"),\n                action: (target) => this.props.resetColumnWidth(target.closest(\"td, th\")),\n            },\n            this.hasCustomTableSize && {\n                name: \"reset_table_size\",\n                icon: \"fa-table\",\n                text: _t(\"Reset table size\"),\n                action: (target) => this.props.resetTableSize(target.closest(\"table\")),\n            },\n            {\n                name: \"clear_content\",\n                icon: \"fa-times-circle\",\n                text: _t(\"Clear content\"),\n                action: this.props.clearColumnContent.bind(this),\n            },\n        ].filter(Boolean);\n    }\n\n    rowItems() {\n        return [\n            this.isFirst &&\n                !this.isTableHeader && {\n                    name: \"make_header\",\n                    icon: \"fa-th-large\",\n                    text: _t(\"Turn into header\"),\n                    action: (target) => this.props.turnIntoHeader(target.parentElement),\n                },\n            this.isFirst &&\n                this.isTableHeader && {\n                    name: \"remove_header\",\n                    icon: \"fa-table\",\n                    text: _t(\"Turn into row\"),\n                    action: (target) => this.props.turnIntoRow(target.parentElement),\n                },\n            !this.isFirst && {\n                name: \"move_up\",\n                icon: \"fa-chevron-up\",\n                text: _t(\"Move up\"),\n                action: (target) => this.props.moveRow(\"up\", target.parentElement),\n            },\n            !this.isLast && {\n                name: \"move_down\",\n                icon: \"fa-chevron-down\",\n                text: _t(\"Move down\"),\n                action: (target) => this.props.moveRow(\"down\", target.parentElement),\n            },\n            !this.isTableHeader && {\n                name: \"insert_above\",\n                icon: \"fa-plus\",\n                text: _t(\"Insert above\"),\n                action: (target) => this.props.addRow(\"before\", target.parentElement),\n            },\n            {\n                name: \"insert_below\",\n                icon: \"fa-plus\",\n                text: _t(\"Insert below\"),\n                action: (target) => this.props.addRow(\"after\", target.parentElement),\n            },\n            {\n                name: \"delete\",\n                icon: \"fa-trash\",\n                text: _t(\"Delete\"),\n                action: (target) => this.props.removeRow(target.parentElement),\n            },\n            this.hasCustomRowHeight && {\n                name: \"reset_row_size\",\n                icon: \"fa-table\",\n                text: _t(\"Reset row size\"),\n                action: (target) => this.props.resetRowHeight(target.closest(\"tr\")),\n            },\n            this.hasCustomTableSize && {\n                name: \"reset_table_size\",\n                icon: \"fa-table\",\n                text: _t(\"Reset table size\"),\n                action: (target) => this.props.resetTableSize(target.closest(\"table\")),\n            },\n            {\n                name: \"clear_content\",\n                icon: \"fa-times-circle\",\n                text: _t(\"Clear content\"),\n                action: (target) => this.props.clearRowContent(target.parentElement),\n            },\n        ].filter(Boolean);\n    }\n}\n", "import { Component, useExternalListener, useState } from \"@odoo/owl\";\n\nexport class TablePicker extends Component {\n    static template = \"html_editor.TablePicker\";\n    static props = {\n        insertTable: Function,\n        editable: {\n            validate: (el) => el.nodeType === Node.ELEMENT_NODE,\n        },\n        overlay: Object,\n        direction: String,\n    };\n\n    setup() {\n        this.state = useState({\n            cols: 3,\n            rows: 3,\n        });\n        useExternalListener(\n            this.props.editable.ownerDocument,\n            \"keydown\",\n            (ev) => {\n                ev.stopPropagation();\n                const key = ev.key;\n                const isRTL = this.props.direction === \"rtl\";\n                switch (key) {\n                    case \"Enter\":\n                        ev.preventDefault();\n                        this.insertTable();\n                        break;\n                    case \"ArrowUp\":\n                        ev.preventDefault();\n                        if (this.state.rows > 1) {\n                            this.state.rows -= 1;\n                        }\n                        break;\n                    case \"ArrowDown\":\n                        this.state.rows += 1;\n                        ev.preventDefault();\n                        break;\n                    case \"ArrowLeft\":\n                        ev.preventDefault();\n                        if (isRTL) {\n                            this.state.cols += 1;\n                        } else {\n                            if (this.state.cols > 1) {\n                                this.state.cols -= 1;\n                            }\n                        }\n                        break;\n                    case \"ArrowRight\":\n                        ev.preventDefault();\n                        if (isRTL) {\n                            if (this.state.cols > 1) {\n                                this.state.cols -= 1;\n                            }\n                        } else {\n                            this.state.cols += 1;\n                        }\n                        break;\n                    default:\n                        ev.stopImmediatePropagation();\n                        this.props.overlay.close();\n                        break;\n                }\n            },\n            { capture: true }\n        );\n    }\n\n    updateSize(cols, rows) {\n        this.state.cols = cols;\n        this.state.rows = rows;\n    }\n\n    insertTable() {\n        this.props.insertTable({ cols: this.state.cols, rows: this.state.rows });\n        this.props.overlay.close();\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { isBlock } from \"@html_editor/utils/blocks\";\nimport {\n    fillEmpty,\n    fillShrunkPhrasingParent,\n    removeClass,\n    splitTextNode,\n} from \"@html_editor/utils/dom\";\nimport {\n    getDeepestPosition,\n    isProtected,\n    isProtecting,\n    isEmptyBlock,\n    isTextNode,\n    nextLeaf,\n    previousLeaf,\n    isTableCell,\n} from \"@html_editor/utils/dom_info\";\nimport {\n    ancestors,\n    closestElement,\n    createDOMPathGenerator,\n    descendants,\n    firstLeaf,\n    lastLeaf,\n} from \"@html_editor/utils/dom_traversal\";\nimport { parseHTML } from \"@html_editor/utils/html\";\nimport { DIRECTIONS, leftPos, rightPos, nodeSize } from \"@html_editor/utils/position\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { findInSelection } from \"@html_editor/utils/selection\";\nimport { getColumnIndex, getRowIndex, getTableCells } from \"@html_editor/utils/table\";\nimport { isBrowserFirefox } from \"@web/core/browser/feature_detection\";\nimport { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport const BORDER_SENSITIVITY = 5;\n\nconst tableInnerComponents = new Set([\"THEAD\", \"TBODY\", \"TFOOT\", \"TR\", \"TH\", \"TD\"]);\nfunction isUnremovableTableComponent(node, root) {\n    if (!tableInnerComponents.has(node.nodeName)) {\n        return false;\n    }\n    if (!root) {\n        return true;\n    }\n    const closestTable = closestElement(node, \"table\");\n    return !root.contains(closestTable);\n}\n\n/**\n * @typedef { Object } TableShared\n * @property { TablePlugin['addColumn'] } addColumn\n * @property { TablePlugin['addRow'] } addRow\n * @property { TablePlugin['turnIntoHeader'] } turnIntoHeader\n * @property { TablePlugin['moveColumn'] } moveColumn\n * @property { TablePlugin['moveRow'] } moveRow\n * @property { TablePlugin['removeColumn'] } removeColumn\n * @property { TablePlugin['removeRow'] } removeRow\n * @property { TablePlugin['turnIntoRow'] } turnIntoRow\n * @property { TablePlugin['resetRowHeight'] } resetRowHeight\n * @property { TablePlugin['resetColumnWidth'] } resetColumnWidth\n * @property { TablePlugin['resetTableSize'] } resetTableSize\n * @property { TablePlugin['clearColumnContent'] } clearColumnContent\n * @property { TablePlugin['clearRowContent'] } clearRowContent\n */\n\n/**\n * @typedef {((el: HTMLElement) => void)[]} deselect_custom_selected_nodes_handlers\n */\n\n/**\n * This plugin only contains the table manipulation and selection features. All UI overlay\n * code is located in the table_ui plugin\n */\nexport class TablePlugin extends Plugin {\n    static id = \"table\";\n    static dependencies = [\n        \"baseContainer\",\n        \"dom\",\n        \"history\",\n        \"selection\",\n        \"delete\",\n        \"split\",\n        \"color\",\n    ];\n    static shared = [\n        \"insertTable\",\n        \"addColumn\",\n        \"addRow\",\n        \"removeColumn\",\n        \"removeRow\",\n        \"moveColumn\",\n        \"turnIntoHeader\",\n        \"turnIntoRow\",\n        \"moveRow\",\n        \"resetRowHeight\",\n        \"resetColumnWidth\",\n        \"resetTableSize\",\n        \"clearColumnContent\",\n        \"clearRowContent\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"insertTable\",\n                run: (params) => {\n                    this.insertTable(params);\n                },\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        toolbar_namespace_providers: [\n            withSequence(\n                90,\n                (targetedNodes, editableSelection) =>\n                    closestElement(editableSelection.anchorNode, \".o_selected_td\") && \"compact\"\n            ),\n        ],\n\n        /** Handlers */\n        selectionchange_handlers: this.updateSelectionTable.bind(this),\n        clipboard_content_processors: this.processContentForClipboard.bind(this),\n        clean_for_save_handlers: ({ root }) => this.deselectTable(root),\n        before_line_break_handlers: this.resetTableSelection.bind(this),\n        before_split_block_handlers: this.resetTableSelection.bind(this),\n\n        /** Overrides */\n        tab_overrides: withSequence(20, this.handleTab.bind(this)),\n        shift_tab_overrides: withSequence(20, this.handleShiftTab.bind(this)),\n        delete_range_overrides: this.handleDeleteRange.bind(this),\n        color_apply_overrides: this.applyTableColor.bind(this),\n\n        unremovable_node_predicates: isUnremovableTableComponent,\n        unsplittable_node_predicates: (node) =>\n            node.nodeName === \"TABLE\" || tableInnerComponents.has(node.nodeName),\n        fully_selected_node_predicates: (node) => !!closestElement(node, \".o_selected_td\"),\n        targeted_nodes_processors: this.adjustTargetedNodes.bind(this),\n        move_node_whitelist_selectors: \"table\",\n        selection_blocker_predicates: (node) => {\n            if (node.nodeName === \"TABLE\") {\n                return true;\n            }\n        },\n        selection_placeholder_container_predicates: (container) => {\n            if (container.nodeName === \"TABLE\") {\n                return false;\n            } else if ([\"TD\", \"TH\"].includes(container.nodeName)) {\n                return true;\n            }\n        },\n        normalize_handlers: this.distributeTableColorsToAllCells.bind(this),\n    };\n\n    setup() {\n        this.addDomListener(this.editable, \"mousedown\", this.onMousedown);\n        this.addDomListener(this.editable, \"mouseup\", this.onMouseup);\n        this.addDomListener(this.editable, \"keydown\", (ev) => {\n            this._isKeyDown = true;\n            const arrowHandled = [\"arrowup\", \"control+arrowup\", \"arrowdown\", \"control+arrowdown\"];\n            if (arrowHandled.includes(getActiveHotkey(ev))) {\n                this.navigateCell(ev);\n            }\n            const shiftArrowHandled = [\n                \"shift+arrowup\",\n                \"shift+arrowright\",\n                \"shift+arrowdown\",\n                \"shift+arrowleft\",\n                \"control+shift+arrowup\",\n                \"control+shift+arrowright\",\n                \"control+shift+arrowdown\",\n                \"control+shift+arrowleft\",\n            ];\n            if (shiftArrowHandled.includes(getActiveHotkey(ev))) {\n                this.isShiftArrowKeyboardSelection = true;\n                this.updateTableKeyboardSelection(ev);\n            }\n        });\n        this.onMousemove = this.onMousemove.bind(this);\n    }\n\n    handleTab() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const inTable = closestElement(selection.anchorNode, \"table\");\n        if (inTable) {\n            // Move cursor to next cell.\n            const shouldAddNewRow = !this.shiftCursorToTableCell(1);\n            if (shouldAddNewRow) {\n                this.addRow(\"after\", findInSelection(selection, \"tr\"));\n                this.shiftCursorToTableCell(1);\n                this.dependencies.history.addStep();\n            }\n            return true;\n        }\n    }\n\n    handleShiftTab() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const inTable = closestElement(selection.anchorNode, \"table\");\n        if (inTable) {\n            // Move cursor to previous cell.\n            this.shiftCursorToTableCell(-1);\n            return true;\n        }\n    }\n\n    /**\n     * Inherits table-level colors to all child tds to make it\n     * easier to add/remove style on tables.\n     *\n     * @param {Element} root\n     */\n    distributeTableColorsToAllCells(root) {\n        [...root.querySelectorAll(\"table\")]\n            .filter((table) => table.style[\"color\"] || table.style[\"backgroundColor\"])\n            .forEach((table) => {\n                const tds = table.querySelectorAll(\"td\");\n                for (const td of tds) {\n                    td.style[\"color\"] = td.style[\"color\"] || table.style[\"color\"];\n                    td.style[\"backgroundColor\"] =\n                        td.style[\"backgroundColor\"] || table.style[\"backgroundColor\"];\n                }\n                table.style[\"color\"] = \"\";\n                table.style[\"backgroundColor\"] = \"\";\n            });\n    }\n\n    createTable({ rows = 2, cols = 2 } = {}) {\n        const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n        fillShrunkPhrasingParent(baseContainer);\n        const baseContainerHtml = baseContainer.outerHTML;\n        const tdsHtml = new Array(cols).fill(`<td>${baseContainerHtml}</td>`).join(\"\");\n        const trsHtml = new Array(rows).fill(`<tr>${tdsHtml}</tr>`).join(\"\");\n        const tableHtml = `<table class=\"table table-bordered o_table\"><tbody>${trsHtml}</tbody></table>`;\n        return parseHTML(this.document, tableHtml);\n    }\n\n    _insertTable({ rows = 2, cols = 2 } = {}) {\n        const newTable = this.createTable({ rows, cols });\n        let sel = this.dependencies.selection.getEditableSelection();\n        if (!sel.isCollapsed) {\n            this.dependencies.delete.deleteSelection();\n        }\n        while (!isBlock(sel.anchorNode)) {\n            const anchorNode = sel.anchorNode;\n            const isTextNode = anchorNode.nodeType === Node.TEXT_NODE;\n            const newAnchorNode = isTextNode\n                ? splitTextNode(anchorNode, sel.anchorOffset, DIRECTIONS.LEFT) + 1 && anchorNode\n                : this.dependencies.split.splitElement(anchorNode, sel.anchorOffset).shift();\n            const newPosition = rightPos(newAnchorNode);\n            sel = this.dependencies.selection.setSelection(\n                { anchorNode: newPosition[0], anchorOffset: newPosition[1] },\n                { normalize: false }\n            );\n        }\n        const [table] = this.dependencies.dom.insert(newTable);\n        return table;\n    }\n    insertTable({ rows = 2, cols = 2 } = {}) {\n        const table = this._insertTable({ rows, cols });\n        this.dependencies.selection.setCursorStart(\n            table.querySelector(baseContainerGlobalSelector)\n        );\n        this.dependencies.history.addStep();\n    }\n    /**\n     * @param {'before'|'after'} position\n     * @param {HTMLTableCellElement} reference\n     */\n    addColumn(position, reference) {\n        const columnIndex = getColumnIndex(reference);\n        const table = closestElement(reference, \"table\");\n        const tableWidth = table.style.width && parseFloat(table.style.width);\n        const referenceColumn = table.querySelectorAll(\n            `tr :is(td, th):nth-of-type(${columnIndex + 1})`\n        );\n        const referenceCellWidth = reference.style.width\n            ? parseFloat(reference.style.width)\n            : reference.clientWidth;\n        // Temporarily set widths so proportions are respected.\n        const firstRow = table.querySelector(\"tr\");\n        const firstRowCells = [...firstRow.children].filter(\n            (child) => child.nodeName === \"TD\" || child.nodeName === \"TH\"\n        );\n        let totalWidth = 0;\n        if (tableWidth) {\n            for (const cell of firstRowCells) {\n                const width = parseFloat(cell.style.width);\n                cell.style.width = width + \"px\";\n                // Spread the widths to preserve proportions.\n                // -1 for the width of the border of the new column.\n                const newWidth = Math.max(\n                    Math.round((width * tableWidth) / (tableWidth + referenceCellWidth - 1)),\n                    13\n                );\n                cell.style.width = newWidth + \"px\";\n                totalWidth += newWidth;\n            }\n        }\n        referenceColumn.forEach((cell, rowIndex) => {\n            const newCell = this.document.createElement(cell.tagName);\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            baseContainer.append(this.document.createElement(\"br\"));\n            newCell.append(baseContainer);\n            cell[position](newCell);\n            // If the first row is a header, ensure the new column's\n            // first cell is also marked as a header (<th>).\n            if (rowIndex === 0 && cell.classList.contains(\"o_table_header\")) {\n                newCell.classList.add(\"o_table_header\");\n            }\n            if (rowIndex === 0 && tableWidth) {\n                newCell.style.width = cell.style.width;\n                totalWidth += parseFloat(cell.style.width);\n            }\n        });\n        if (tableWidth) {\n            if (totalWidth !== tableWidth - 1) {\n                // -1 for the width of the border of the new column.\n                firstRowCells[firstRowCells.length - 1].style.width =\n                    parseFloat(firstRowCells[firstRowCells.length - 1].style.width) +\n                    (tableWidth - totalWidth - 1) +\n                    \"px\";\n            }\n            // Fix the table and row's width so it doesn't change.\n            table.style.width = tableWidth + \"px\";\n        }\n    }\n    /**\n     * @param {'before'|'after'} position\n     * @param {HTMLTableRowElement} reference\n     */\n    addRow(position, reference) {\n        const referenceRowHeight = reference.style.height && parseFloat(reference.style.height);\n        const newRow = this.document.createElement(\"tr\");\n        if (referenceRowHeight) {\n            newRow.style.height = referenceRowHeight + \"px\";\n        }\n        const cells = reference.querySelectorAll(\"td, th\");\n        const referenceRowWidths = [...cells].map((cell) => cell.style.width);\n        newRow.append(\n            ...Array.from(cells).map(() => {\n                const td = this.document.createElement(\"td\");\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                baseContainer.append(this.document.createElement(\"br\"));\n                td.append(baseContainer);\n                return td;\n            })\n        );\n        reference[position](newRow);\n        if (referenceRowHeight) {\n            newRow.style.height = referenceRowHeight + \"px\";\n        }\n        // Preserve the width of the columns (applied only on the first row).\n        if (getRowIndex(newRow) === 0) {\n            let columnIndex = 0;\n            for (const column of newRow.children) {\n                column.style.width = referenceRowWidths[columnIndex];\n                cells[columnIndex].style.width = \"\";\n                columnIndex++;\n            }\n        }\n    }\n    /**\n     * @param {HTMLTableRowElement} reference\n     */\n    turnIntoHeader(reference) {\n        const preserveSelection = this.dependencies.selection.preserveSelection();\n        [...reference.children].forEach((td) => {\n            if (td.nodeName == \"TD\") {\n                const th = this.document.createElement(\"th\");\n                if (td.style?.cssText.length) {\n                    th.style.cssText = td.style?.cssText;\n                }\n                th.classList.add(\"o_table_header\");\n                th.append(...td.childNodes);\n                td.replaceWith(th);\n            }\n        });\n        preserveSelection.restore();\n    }\n    /**\n     * @param {HTMLTableRowElement} reference\n     */\n    turnIntoRow(reference) {\n        const preserveSelection = this.dependencies.selection.preserveSelection();\n        [...reference.children].forEach((th) => {\n            if (th.nodeName == \"TH\") {\n                const td = this.document.createElement(\"td\");\n                if (th.style?.cssText.length) {\n                    td.style.cssText = th.style?.cssText;\n                }\n                td.append(...th.childNodes);\n                th.replaceWith(td);\n            }\n        });\n        preserveSelection.restore();\n    }\n    /**\n     * @param {HTMLTableCellElement} cell\n     */\n    removeColumn(cell) {\n        const table = closestElement(cell, \"table\");\n        const cells = [...closestElement(cell, \"tr\").querySelectorAll(\"th, td\")];\n        const index = cells.findIndex((td) => td === cell);\n        const siblingCell = cells[index - 1] || cells[index + 1];\n        table\n            .querySelectorAll(`tr :is(td, th):nth-of-type(${index + 1})`)\n            .forEach((td) => td.remove());\n        // not sure we should move the cursor?\n        siblingCell\n            ? this.dependencies.selection.setCursorStart(siblingCell)\n            : this.deleteTable(table);\n    }\n    /**\n     * @param {HTMLTableRowElement} row\n     */\n    removeRow(row) {\n        const table = closestElement(row, \"table\");\n        const siblingRow = row.previousElementSibling || row.nextElementSibling;\n        row.remove();\n        // not sure we should move the cursor?\n        siblingRow\n            ? this.dependencies.selection.setCursorStart(siblingRow.querySelector(\"td, th\"))\n            : this.deleteTable(table);\n    }\n    /**\n     * @param {'left'|'right'} position\n     * @param {HTMLTableCellElement} cell\n     */\n    moveColumn(position, cell) {\n        const columnIndex = getColumnIndex(cell);\n        const nColumns = cell.parentElement.children.length;\n        if (\n            columnIndex < 0 ||\n            (position === \"left\" && columnIndex === 0) ||\n            (position !== \"left\" && columnIndex === nColumns - 1)\n        ) {\n            return;\n        }\n\n        const trs = cell.parentElement.parentElement.children;\n        const tdsToMove = [...trs].map((tr) => tr.children[columnIndex]);\n        const selectionToRestore = this.dependencies.selection.getEditableSelection();\n        if (position === \"left\") {\n            tdsToMove.forEach((td) => td.previousElementSibling.before(td));\n        } else {\n            tdsToMove.forEach((td) => td.nextElementSibling.after(td));\n        }\n        this.dependencies.selection.setSelection(selectionToRestore);\n    }\n    /**\n     * @param {'up'|'down'} position\n     * @param {HTMLTableRowElement} row\n     */\n    moveRow(position, row) {\n        const selectionToRestore = this.dependencies.selection.getEditableSelection();\n        let adjustedRow;\n        if (position === \"up\") {\n            const isPreviousRowHeader =\n                [...row.previousElementSibling.children][0].nodeName === \"TH\";\n            row.previousElementSibling?.before(row);\n            adjustedRow = row;\n            if (isPreviousRowHeader) {\n                this.turnIntoHeader(row);\n                this.turnIntoRow(row.nextElementSibling);\n            }\n        } else {\n            const isRowHeader = [...row.children][0].nodeName === \"TH\";\n            row.nextElementSibling?.after(row);\n            adjustedRow = row.previousElementSibling;\n            if (isRowHeader) {\n                this.turnIntoHeader(adjustedRow);\n                this.turnIntoRow(row);\n            }\n        }\n\n        // If the moved row becomes the first row, copy the widths of its td\n        // elements from the previous first row, as td widths are only applied\n        // to the first row.\n        if (!adjustedRow.previousElementSibling) {\n            adjustedRow.childNodes.forEach((cell, index) => {\n                cell.style.width = adjustedRow.nextElementSibling.childNodes[index].style.width;\n            });\n        }\n        this.dependencies.selection.setSelection(selectionToRestore);\n    }\n\n    /**\n     * @param {HTMLTableElement} table\n     */\n    normalizeRowHeight(table) {\n        const rows = [...table.rows];\n        const referenceRow = rows.find((row) => !row.style.height);\n        const referenceRowHeight = parseFloat(getComputedStyle(referenceRow).height);\n        rows.forEach((row) => {\n            if (\n                row.style.height &&\n                Math.abs(parseFloat(row.style.height) - referenceRowHeight) <= 1\n            ) {\n                row.style.height = \"\";\n            }\n        });\n    }\n\n    /**\n     * @param {HTMLTableRowElement} row\n     */\n    resetRowHeight(row) {\n        const table = closestElement(row, \"table\");\n        row.style.height = \"\";\n        this.normalizeRowHeight(table);\n    }\n\n    /**\n     * @param {HTMLTableElement} table\n     */\n    normalizeColumnWidth(table) {\n        const rows = [...table.rows];\n        const firstRowCells = [...rows[0].cells];\n        const tableWidth = parseFloat(table.style.width);\n        if (tableWidth) {\n            const expectedCellWidth = tableWidth / firstRowCells.length;\n            firstRowCells.forEach((cell, i) => {\n                const cellWidth = parseFloat(cell.style.width);\n                if (cellWidth && Math.abs(cellWidth - expectedCellWidth) <= 1) {\n                    rows.forEach((row) => (row.cells[i].style.width = \"\"));\n                }\n            });\n        }\n    }\n\n    /**\n     * @param {HTMLTableCellElement} cell\n     */\n    resetColumnWidth(cell) {\n        const currentCellWidth = parseFloat(cell.style.width);\n        if (!currentCellWidth) {\n            return;\n        }\n\n        const table = closestElement(cell, \"table\");\n        const tableWidth = parseFloat(table.style.width);\n        const currentRow = cell.parentElement;\n        const currentRowCells = [...currentRow.cells];\n        const rowCellCount = currentRowCells.length;\n        const expectedCellWidth = tableWidth / rowCellCount;\n        const widthDifference = currentCellWidth - expectedCellWidth;\n        const currentColumnIndex = getColumnIndex(cell);\n\n        let totalWidthLeftOfCell = 0,\n            totalWidthRightOfCell = 0;\n        currentRowCells.forEach((rowCell, i) => {\n            const cellWidth = parseFloat(rowCell.style.width) || rowCell.clientWidth;\n            if (i < currentColumnIndex) {\n                totalWidthLeftOfCell += cellWidth;\n            } else if (i > currentColumnIndex) {\n                totalWidthRightOfCell += cellWidth;\n            }\n        });\n\n        let expectedWidthLeftOfCell = currentColumnIndex * expectedCellWidth;\n        let expectedWidthRightOfCell = (rowCellCount - 1 - currentColumnIndex) * expectedCellWidth;\n        let cellsToAdjust = [];\n        for (\n            let i = currentColumnIndex - 1;\n            i >= 0 && Math.abs(expectedWidthLeftOfCell - totalWidthLeftOfCell) > 1;\n            i--\n        ) {\n            cellsToAdjust.push(currentRowCells[i]);\n            totalWidthLeftOfCell -=\n                parseFloat(currentRowCells[i].style.width) || currentRowCells[i].clientWidth;\n            expectedWidthLeftOfCell -= expectedCellWidth;\n        }\n        for (\n            let j = currentColumnIndex + 1;\n            j < rowCellCount && Math.abs(expectedWidthRightOfCell - totalWidthRightOfCell) > 1;\n            j++\n        ) {\n            cellsToAdjust.push(currentRowCells[j]);\n            totalWidthRightOfCell -=\n                parseFloat(currentRowCells[j].style.width) || currentRowCells[j].clientWidth;\n            expectedWidthRightOfCell -= expectedCellWidth;\n        }\n\n        cellsToAdjust = cellsToAdjust.filter((adjCell) => {\n            const cellWidth = parseFloat(adjCell.style.width) || adjCell.clientWidth;\n            return widthDifference > 0\n                ? cellWidth < expectedCellWidth\n                : cellWidth > expectedCellWidth;\n        });\n\n        const totalWidthForAdjustment = cellsToAdjust.reduce((width, adjCell) => {\n            const cellWidth = parseFloat(adjCell.style.width) || adjCell.clientWidth;\n            return width + Math.abs(expectedCellWidth - cellWidth);\n        }, 0);\n\n        cell.style.width = `${expectedCellWidth}px`;\n        cellsToAdjust.forEach((adjCell) => {\n            const adjCellWidth = parseFloat(adjCell.style.width) || adjCell.clientWidth;\n            const adjustmentWidth =\n                (Math.abs(expectedCellWidth - adjCellWidth) / totalWidthForAdjustment) *\n                Math.abs(widthDifference);\n            adjCell.style.width = `${\n                adjCellWidth + (widthDifference > 0 ? adjustmentWidth : -adjustmentWidth)\n            }px`;\n        });\n        this.normalizeColumnWidth(table);\n    }\n\n    /**\n     * @param {HTMLTableElement} table\n     */\n    resetTableSize(table) {\n        table.removeAttribute(\"style\");\n        const cells = [...table.querySelectorAll(\"tr, td, th\")];\n        cells.forEach((cell) => {\n            const cStyle = cell.style;\n            if (cell.tagName === \"TR\") {\n                cStyle.height = \"\";\n            } else {\n                cStyle.width = \"\";\n            }\n        });\n    }\n    /**\n     * @param {HTMLTableCellElement} cell\n     */\n    clearColumnContent(cell) {\n        const table = closestElement(cell, \"table\");\n        const cells = [...closestElement(cell, \"tr\").querySelectorAll(\"th, td\")];\n        const index = cells.findIndex((td) => td === cell);\n        table.querySelectorAll(`tr :is(td, th):nth-of-type(${index + 1})`).forEach((td) => {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            fillEmpty(baseContainer);\n            td.replaceChildren(baseContainer);\n        });\n    }\n    /**\n     * @param {HTMLTableRowElement} row\n     */\n    clearRowContent(row) {\n        row.querySelectorAll(\"td, th\").forEach((td) => {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            fillEmpty(baseContainer);\n            td.replaceChildren(baseContainer);\n        });\n    }\n    deleteTable(table) {\n        table =\n            table || findInSelection(this.dependencies.selection.getEditableSelection(), \"table\");\n        if (!table) {\n            return;\n        }\n        const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n        baseContainer.appendChild(this.document.createElement(\"br\"));\n        table.before(baseContainer);\n        table.remove();\n        this.dependencies.selection.setCursorStart(baseContainer);\n    }\n\n    // @todo @phoenix: handle deleteBackward on table cells\n    // deleteBackwardBefore({ targetNode, targetOffset }) {\n    //     // If the cursor is at the beginning of a row, prevent deletion.\n    //     if (targetNode.nodeType === Node.ELEMENT_NODE && isRow(targetNode) && !targetOffset) {\n    //         return true;\n    //     }\n    // }\n\n    /**\n     * Removes fully selected rows or columns, clears the content of selected\n     * cells otherwise.\n     *\n     * @param {NodeListOf<HTMLTableCellElement>} selectedTds - Non-empty\n     * NodeList of selected table cells.\n     */\n    deleteTableCells(selectedTds) {\n        const rows = [...closestElement(selectedTds[0], \"tr\").parentElement.children].filter(\n            (child) => child.nodeName === \"TR\"\n        );\n        const firstRowCells = [...rows[0].children].filter(\n            (child) => child.nodeName === \"TD\" || child.nodeName === \"TH\"\n        );\n        const firstCellRowIndex = getRowIndex(selectedTds[0]);\n        const firstCellColumnIndex = getColumnIndex(selectedTds[0]);\n        const lastCellRowIndex = getRowIndex(selectedTds[selectedTds.length - 1]);\n        const lastCellColumnIndex = getColumnIndex(selectedTds[selectedTds.length - 1]);\n\n        const areFullColumnsSelected =\n            firstCellRowIndex === 0 && lastCellRowIndex === rows.length - 1;\n        const areFullRowsSelected =\n            firstCellColumnIndex === 0 && lastCellColumnIndex === firstRowCells.length - 1;\n\n        if (areFullColumnsSelected) {\n            for (let index = firstCellColumnIndex; index <= lastCellColumnIndex; index++) {\n                this.removeColumn(firstRowCells[index]);\n            }\n            return;\n        }\n\n        if (areFullRowsSelected) {\n            for (let index = firstCellRowIndex; index <= lastCellRowIndex; index++) {\n                this.removeRow(rows[index]);\n            }\n            return;\n        }\n\n        for (const td of selectedTds) {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            baseContainer.appendChild(this.document.createElement(\"br\"));\n            td.replaceChildren(baseContainer);\n        }\n        this.dependencies.selection.setCursorStart(selectedTds[0].firstChild);\n    }\n\n    /**\n     * @param {Object} range - Range-like object.\n     * @param {Array} fullySelectedTables - Non-empty array of table elements.\n     */\n    deleteRangeWithFullySelectedTables(range, fullySelectedTables) {\n        let { startContainer, startOffset, endContainer, endOffset } = range;\n\n        // Expand range to fully include tables.\n        const firstTable = fullySelectedTables[0];\n        if (firstTable.contains(startContainer)) {\n            [startContainer, startOffset] = leftPos(firstTable);\n        }\n        const lastTable = fullySelectedTables.at(-1);\n        if (lastTable.contains(endContainer)) {\n            [endContainer, endOffset] = rightPos(lastTable);\n        }\n        range = { startContainer, startOffset, endContainer, endOffset };\n\n        range = this.dependencies.delete.deleteRange(range);\n\n        // Normalize deep.\n        // @todo @phoenix: Use something from the selection plugin (normalize deep?)\n        const [anchorNode, anchorOffset] = getDeepestPosition(\n            range.startContainer,\n            range.startOffset\n        );\n\n        this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n    }\n\n    handleDeleteRange(range) {\n        // @todo @phoenix: this does not depend on the range. This should be\n        // optimized by keeping in memory the state of selected cells/tables.\n        const fullySelectedTables = [...this.editable.querySelectorAll(\".o_selected_table\")].filter(\n            (table) =>\n                [...table.querySelectorAll(\"td, th\")].every(\n                    (td) =>\n                        closestElement(td, \"table\") !== table ||\n                        td.classList.contains(\"o_selected_td\")\n                )\n        );\n        if (fullySelectedTables.length) {\n            this.deleteRangeWithFullySelectedTables(range, fullySelectedTables);\n            return true;\n        }\n\n        const selectedTds = this.editable.querySelectorAll(\".o_selected_td\");\n        if (selectedTds.length) {\n            this.deleteTableCells(selectedTds);\n            // this._toggleTableUi();\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Moves the cursor by shiftIndex table cells.\n     *\n     * @param {Number} shiftIndex - The index to shift the cursor by.\n     * @returns {boolean} - True if the cursor was successfully moved, false otherwise.\n     */\n    shiftCursorToTableCell(shiftIndex) {\n        const sel = this.dependencies.selection.getEditableSelection();\n        const currentTd = closestElement(sel.anchorNode, isTableCell);\n        const closestTable = closestElement(currentTd, \"table\");\n        if (!currentTd || !closestTable) {\n            return false;\n        }\n        const tds = [...closestTable.querySelectorAll(\"td, th\")];\n        const cursorDestination = tds[tds.findIndex((td) => currentTd === td) + shiftIndex];\n        if (!cursorDestination) {\n            return false;\n        }\n        this.dependencies.selection.setCursorEnd(lastLeaf(cursorDestination));\n        return true;\n    }\n\n    hanldeFirefoxSelection(ev = null) {\n        const selection = this.document.getSelection();\n        if (isBrowserFirefox()) {\n            if (!this.dependencies.selection.isSelectionInEditable(selection)) {\n                return false;\n            }\n            if (selection.rangeCount > 1 || selection.anchorNode?.tagName === \"TR\") {\n                // In Firefox, selecting multiple cells within a table using the mouse can create multiple ranges.\n                // This behavior can cause the original selection (where the selection started) to be lost.\n                // To solve the issue we merge the ranges of the selection together the first time we find\n                // selection.rangeCount > 1. Morover, when hitting a double click on a cell, it spans a row\n                // inside selection which needs to be simplified here.\n                let [anchorNode, anchorOffset] = getDeepestPosition(\n                    selection.getRangeAt(0).startContainer,\n                    selection.getRangeAt(0).startOffset\n                );\n                let [focusNode, focusOffset] = getDeepestPosition(\n                    selection.getRangeAt(selection.rangeCount - 1).startContainer,\n                    selection.getRangeAt(selection.rangeCount - 1).startOffset\n                );\n                if (this.selectionDirection === \"backward\") {\n                    [anchorNode, focusNode] = [focusNode, anchorNode];\n                    [anchorOffset, focusOffset] = [focusOffset, anchorOffset];\n                }\n                this.dependencies.selection.setSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                });\n                return true;\n            } else if (\n                ev &&\n                closestElement(ev.target, \"table\") ===\n                    closestElement(selection.anchorNode, \"table\") &&\n                closestElement(ev.target, isTableCell) !==\n                    closestElement(selection.focusNode, isTableCell)\n            ) {\n                // After the manual update firefox will not be able the table selection automatically\n                // so we need to update the selection manually too.\n                // When we hover on a new table cell we mark it as the new focusNode.\n                this.dependencies.selection.setSelection({\n                    anchorNode: selection.anchorNode,\n                    anchorOffset: selection.anchorOffset,\n                    focusNode: ev.target,\n                    focusOffset: 0,\n                });\n                this.selectionDirection = selection.direction;\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Sets selection in table to make cell selection\n     * rectangularly when pressing shift + arrow key.\n     *\n     * @private\n     * @param {KeyboardEvent} ev\n     */\n    updateTableKeyboardSelection(ev) {\n        const selection = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        const startTable = closestElement(selection.anchorNode, \"table\");\n        const endTable = closestElement(selection.focusNode, \"table\");\n        if (!(startTable || endTable)) {\n            return;\n        }\n        const [startTd, endTd] = [\n            closestElement(selection.anchorNode, isTableCell),\n            closestElement(selection.focusNode, isTableCell),\n        ];\n        if (startTable !== endTable) {\n            // Deselect the table if it was fully selected.\n            if (endTable) {\n                const deselectingBackward =\n                    [\"ArrowLeft\", \"ArrowUp\"].includes(ev.key) &&\n                    selection.direction === DIRECTIONS.RIGHT;\n                const deselectingForward =\n                    [\"ArrowRight\", \"ArrowDown\"].includes(ev.key) &&\n                    selection.direction === DIRECTIONS.LEFT;\n                let targetNode;\n                if (deselectingBackward) {\n                    targetNode = endTable.previousElementSibling;\n                } else if (deselectingForward) {\n                    targetNode = endTable.nextElementSibling;\n                }\n                if (targetNode) {\n                    ev.preventDefault();\n                    this.dependencies.selection.setSelection({\n                        anchorNode: selection.anchorNode,\n                        anchorOffset: selection.anchorOffset,\n                        focusNode: targetNode,\n                        focusOffset: deselectingBackward ? nodeSize(targetNode) : 0,\n                    });\n                }\n            }\n            return;\n        }\n        // Handle selection for the single cell.\n        if (startTd === endTd && !startTd.classList.contains(\"o_selected_td\")) {\n            const { focusNode, focusOffset } = selection;\n            // Do not prevent default when there is a text in cell.\n            if (focusNode.nodeType === Node.TEXT_NODE) {\n                const textNodes = descendants(startTd).filter(isTextNode);\n                const lastTextChild = textNodes[textNodes.length - 1];\n                const firstTextChild = textNodes[0];\n                const isAtTextBoundary = {\n                    ArrowRight: nodeSize(focusNode) === focusOffset && focusNode === lastTextChild,\n                    ArrowLeft: focusOffset === 0 && focusNode === firstTextChild,\n                    ArrowUp: focusNode === firstTextChild,\n                    ArrowDown: focusNode === lastTextChild,\n                };\n                if (isAtTextBoundary[ev.key]) {\n                    ev.preventDefault();\n                    this.selectTableCells(this.dependencies.selection.getEditableSelection());\n                }\n            } else {\n                ev.preventDefault();\n                this.selectTableCells(this.dependencies.selection.getEditableSelection());\n            }\n            return;\n        }\n        // Select cells symmetrically.\n        const endCellPosition = { x: getRowIndex(endTd), y: getColumnIndex(endTd) };\n        const tds = [...startTable.rows].map((row) => [...row.cells]);\n        let targetTd, targetNode;\n        switch (ev.key) {\n            case \"ArrowUp\": {\n                if (endCellPosition.x > 0) {\n                    targetTd = tds[endCellPosition.x - 1][endCellPosition.y];\n                } else {\n                    targetNode = previousLeaf(startTable, this.editable);\n                }\n                break;\n            }\n            case \"ArrowDown\": {\n                if (endCellPosition.x < tds.length - 1) {\n                    targetTd = tds[endCellPosition.x + 1][endCellPosition.y];\n                } else {\n                    targetNode = nextLeaf(startTable, this.editable);\n                }\n                break;\n            }\n            case \"ArrowRight\": {\n                if (endCellPosition.y < tds[0].length - 1) {\n                    targetTd = tds[endCellPosition.x][endCellPosition.y + 1];\n                }\n                break;\n            }\n            case \"ArrowLeft\": {\n                if (endCellPosition.y > 0) {\n                    targetTd = tds[endCellPosition.x][endCellPosition.y - 1];\n                }\n                break;\n            }\n        }\n        if (targetTd || targetNode) {\n            this.dependencies.selection.setSelection({\n                anchorNode: selection.anchorNode,\n                anchorOffset: selection.anchorOffset,\n                focusNode: targetTd || targetNode,\n                focusOffset: 0,\n            });\n        }\n        ev.preventDefault();\n    }\n\n    updateSelectionTable(selectionData) {\n        if (\n            this.hanldeFirefoxSelection() ||\n            this._isFirefoxDoubleMousedown ||\n            this._isTripleClickInTable\n        ) {\n            // It will be retriggered with selectionchange\n            delete this._isFirefoxDoubleMousedown;\n            delete this._isTripleClickInTable;\n            return;\n        }\n        if (!selectionData.documentSelectionIsInEditable) {\n            return;\n        }\n        const selection = selectionData.editableSelection;\n        const startTd = closestElement(selection.startContainer, isTableCell);\n        const endTd = closestElement(selection.endContainer, isTableCell);\n        const selectSingleCell =\n            startTd &&\n            startTd === endTd &&\n            startTd.classList.contains(\"o_selected_td\") &&\n            this.isShiftArrowKeyboardSelection;\n        if (!(startTd && startTd === endTd) || this._isKeyDown) {\n            delete this._isKeyDown;\n            // Prevent deselecting single cell unless selection changes\n            // through keyboard.\n            this.deselectTable();\n        }\n        delete this.isShiftArrowKeyboardSelection;\n        const startTable = ancestors(selection.startContainer, this.editable)\n            .filter((node) => node.nodeName === \"TABLE\")\n            .pop();\n        const endTable = ancestors(selection.endContainer, this.editable)\n            .filter((node) => node.nodeName === \"TABLE\")\n            .pop();\n\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        if ((startTd !== endTd || selectSingleCell) && startTable === endTable) {\n            if (!isProtected(startTable) && !isProtecting(startTable)) {\n                // The selection goes through at least two different cells ->\n                // select cells.\n                // Select single cell if selection goes from two cells to\n                // one using shift + arrow key.\n                this.selectTableCells(selection);\n            }\n        } else if (!targetedNodes.every((node) => closestElement(node.parentElement, \"table\"))) {\n            const endSelectionTable = closestElement(selection.focusNode, \"table\");\n            const endSelectionTableTds = endSelectionTable && getTableCells(endSelectionTable);\n            const targetedTds = new Set(\n                targetedNodes.map((node) => closestElement(node, isTableCell))\n            );\n            const isTableFullySelected = endSelectionTableTds?.every((td) => targetedTds.has(td));\n            if (endSelectionTable && !isTableFullySelected) {\n                // Make sure all the cells are targeted in actual selection\n                // when selecting full table. If not, they will be selected\n                // forcefully and updateSelectionTable will be called again.\n                const targetTd =\n                    selection.direction === DIRECTIONS.RIGHT\n                        ? endSelectionTableTds.pop()\n                        : endSelectionTableTds.shift();\n                this.dependencies.selection.setSelection({\n                    anchorNode: selection.anchorNode,\n                    anchorOffset: selection.anchorOffset,\n                    focusNode: targetTd,\n                    focusOffset: selection.direction === DIRECTIONS.RIGHT ? nodeSize(targetTd) : 0,\n                });\n            }\n            const targetedTables = new Set(\n                targetedNodes\n                    .map((node) => closestElement(node, \"table\"))\n                    .filter((node) => node && !isProtected(node) && !isProtecting(node))\n            );\n            for (const table of targetedTables) {\n                // Don't apply several nested levels of selection.\n                if (!ancestors(table, this.editable).some((node) => targetedTables.has(node))) {\n                    table.classList.toggle(\"o_selected_table\", true);\n                    for (const td of getTableCells(table)) {\n                        td.classList.toggle(\"o_selected_td\", true);\n                        this.dispatchTo(\"deselect_custom_selected_nodes_handlers\", td);\n                    }\n                }\n            }\n        }\n    }\n\n    onMousedown(ev) {\n        this._currentMouseState = ev.type;\n        this._lastMousedownPosition = [ev.x, ev.y];\n        const isPointerInsideCell = this.isPointerInsideCell(ev);\n        const td = closestElement(ev.target, isTableCell);\n        if (isPointerInsideCell) {\n            if (\n                !isProtected(td) &&\n                !isProtecting(td) &&\n                ((isEmptyBlock(td) && ev.detail === 2) || ev.detail === 3)\n            ) {\n                this.hanldeFirefoxSelection();\n                this.selectTableCells(this.dependencies.selection.getEditableSelection());\n                if (isBrowserFirefox()) {\n                    // In firefox, selection changes when hitting mouseclick\n                    // second time in an empty cell. It calls updateSelectionTable\n                    // which deselects the single cell. Hence, we need a label\n                    // to keep it selected.\n                    this._isFirefoxDoubleMousedown = true;\n                }\n                if (ev.detail === 3) {\n                    // Doing a tripleclick on a text will change the selection.\n                    // In such case updateSelectionTable should not do anything.\n                    this._isTripleClickInTable = true;\n                }\n            } else {\n                this.editable.addEventListener(\"mousemove\", this.onMousemove);\n                const currentSelection = this.dependencies.selection.getEditableSelection();\n                // disable dragging on table\n                if (closestElement(ev.target, \"td.o_selected_td\")) {\n                    this.dependencies.selection.setCursorStart(currentSelection.anchorNode);\n                }\n                this.deselectTable();\n            }\n        }\n    }\n\n    onMouseup(ev) {\n        delete this._mouseMovePositionWhenAllContentsSelected;\n        this._currentMouseState = ev.type;\n        this.editable.removeEventListener(\"mousemove\", this.onMousemove);\n    }\n\n    /**\n     * Checks if mouse is effectively inside the cell and not overlapping\n     * the cell borders to prevent cell selection while resizing table.\n     *\n     * @param {MouseEvent} ev\n     * @returns {Boolean}\n     */\n    isPointerInsideCell(ev) {\n        const td = closestElement(ev.target, isTableCell);\n        if (td) {\n            const targetRect = td.getBoundingClientRect();\n            if (\n                ev.clientX > targetRect.x + BORDER_SENSITIVITY &&\n                ev.clientX < targetRect.x + td.clientWidth - BORDER_SENSITIVITY &&\n                ev.clientY > targetRect.y + BORDER_SENSITIVITY &&\n                ev.clientY < targetRect.y + td.clientHeight - BORDER_SENSITIVITY\n            ) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    onMousemove(ev) {\n        if (this._currentMouseState !== \"mousedown\") {\n            return;\n        }\n        if (this.hanldeFirefoxSelection(ev)) {\n            return;\n        }\n        const selection = this.dependencies.selection.getEditableSelection();\n        const startTd = closestElement(selection.startContainer, isTableCell);\n        const endTd = closestElement(selection.endContainer, isTableCell);\n        if (startTd && startTd === endTd && !isProtected(startTd) && !isProtecting(startTd)) {\n            const selectedNodes = this.dependencies.selection\n                .getTargetedNodes()\n                .filter(this.dependencies.selection.areNodeContentsFullySelected);\n            const cellContents = descendants(startTd);\n            const areCellContentsFullySelected = cellContents\n                .filter((d) => !isBlock(d))\n                .every((child) => selectedNodes.includes(child));\n            if (areCellContentsFullySelected) {\n                const SENSITIVITY = 5;\n                if (!this._mouseMovePositionWhenAllContentsSelected) {\n                    this._mouseMovePositionWhenAllContentsSelected = [ev.clientX, ev.clientY];\n                }\n                const isMovingAwayFromSelection =\n                    Math.abs(ev.clientX - this._mouseMovePositionWhenAllContentsSelected[0]) >=\n                    SENSITIVITY;\n                if (isMovingAwayFromSelection) {\n                    // A cell is fully selected and the mouse is moving away\n                    // from the selection, within said cell -> select the cell.\n                    this.selectTableCells(selection);\n                }\n            } else if (\n                cellContents.filter(isBlock).every(isEmptyBlock) &&\n                Math.abs(\n                    ev.clientX -\n                        (this._lastMousedownPosition ? this._lastMousedownPosition[0] : ev.clientX)\n                ) >= 20\n            ) {\n                // Handle selecting an empty cell.\n                this.selectTableCells(selection);\n            }\n        }\n    }\n\n    navigateCell(ev) {\n        const selection = this.dependencies.selection.getSelectionData().deepEditableSelection;\n        const anchorNode = selection.anchorNode;\n        const currentCell = closestElement(anchorNode, isTableCell);\n        const currentTable = closestElement(anchorNode, \"table\");\n        if (!selection.isCollapsed || !currentCell) {\n            return;\n        }\n        const isArrowUp = ev.key === \"ArrowUp\";\n        const cellPosition = {\n            row: getRowIndex(currentCell),\n            col: getColumnIndex(currentCell),\n        };\n        const tableRows = [...currentTable.rows].map((row) => [...row.cells]);\n        const shouldNavigateCell = (currentNode) => {\n            const siblingDirection = isArrowUp ? \"previousElementSibling\" : \"nextElementSibling\";\n            const direction = isArrowUp ? DIRECTIONS.LEFT : DIRECTIONS.RIGHT;\n            const domPath = createDOMPathGenerator(direction, {\n                stopTraverseFunction: (node) => node === currentCell,\n                stopFunction: (node) => node === currentCell,\n            });\n            const domPathNode = domPath(currentNode);\n            let node = domPathNode.next().value;\n            while (node) {\n                if ((isBlock(node) && node[siblingDirection]) || node.nodeName === \"BR\") {\n                    return false;\n                }\n                node = domPathNode.next().value;\n            }\n            return true;\n        };\n        const rowOffset = isArrowUp ? -1 : 1;\n        let targetNode = tableRows[cellPosition.row + rowOffset]?.[cellPosition.col];\n        const siblingElement = isArrowUp\n            ? currentTable.previousElementSibling\n            : currentTable.nextElementSibling;\n        if (!targetNode && siblingElement) {\n            // If no target cell is available, navigate to sibling element\n            targetNode = siblingElement;\n        }\n        if (shouldNavigateCell(anchorNode)) {\n            ev.preventDefault();\n            if (targetNode) {\n                targetNode = isArrowUp ? lastLeaf(targetNode) : firstLeaf(targetNode);\n                const targetOffset = isArrowUp ? nodeSize(targetNode) : 0;\n                this.dependencies.selection.setSelection({\n                    anchorNode: targetNode,\n                    anchorOffset: targetOffset,\n                });\n            }\n        }\n    }\n\n    selectTableCells(selection) {\n        const table = closestElement(selection.commonAncestorContainer, \"table\");\n        if (!table) {\n            return;\n        }\n        table.classList.toggle(\"o_selected_table\", true);\n        const columns = getTableCells(table);\n        const startCol =\n            [selection.startContainer, ...ancestors(selection.startContainer, this.editable)].find(\n                (node) => isTableCell(node) && closestElement(node, \"table\") === table\n            ) || columns[0];\n        const endCol =\n            [selection.endContainer, ...ancestors(selection.endContainer, this.editable)].find(\n                (node) => isTableCell(node) && closestElement(node, \"table\") === table\n            ) || columns[columns.length - 1];\n        const [startRow, endRow] = [closestElement(startCol, \"tr\"), closestElement(endCol, \"tr\")];\n        const [startColIndex, endColIndex] = [getColumnIndex(startCol), getColumnIndex(endCol)];\n        const [startRowIndex, endRowIndex] = [getRowIndex(startRow), getRowIndex(endRow)];\n        const [minRowIndex, maxRowIndex] = [\n            Math.min(startRowIndex, endRowIndex),\n            Math.max(startRowIndex, endRowIndex),\n        ];\n        const [minColIndex, maxColIndex] = [\n            Math.min(startColIndex, endColIndex),\n            Math.max(startColIndex, endColIndex),\n        ];\n        // Create an array of arrays of tds (each of which is a row).\n        const grid = [...table.querySelectorAll(\"tr\")]\n            .filter((tr) => closestElement(tr, \"table\") === table)\n            .map((tr) => [...tr.children].filter(isTableCell));\n        for (const tds of grid.filter((_, index) => index >= minRowIndex && index <= maxRowIndex)) {\n            for (const td of tds.filter(\n                (_, index) => index >= minColIndex && index <= maxColIndex\n            )) {\n                td.classList.toggle(\"o_selected_td\", true);\n                this.dispatchTo(\"deselect_custom_selected_nodes_handlers\", td);\n            }\n        }\n    }\n\n    /**\n     * Remove any custom table selection from the editor.\n     *\n     * @returns {boolean} true if a table was deselected\n     */\n    deselectTable(root = this.editable) {\n        let didDeselectTable = false;\n        for (const table of root.querySelectorAll(\".o_selected_table\")) {\n            removeClass(table, \"o_selected_table\");\n            for (const td of table.querySelectorAll(\".o_selected_td\")) {\n                removeClass(td, \"o_selected_td\");\n            }\n            didDeselectTable = true;\n        }\n        return didDeselectTable;\n    }\n\n    applyTableColor(color, mode, previewMode) {\n        const selectedTds = [...this.editable.querySelectorAll(\".o_selected_td\")].filter(\n            (node) => node.isContentEditable\n        );\n        if (selectedTds.length && (mode === \"backgroundColor\" || (mode === \"color\" && !color))) {\n            // Disable the `box-shadow` while previewing the background color.\n            selectedTds.forEach((td) =>\n                td.classList.toggle(\"o_selected_td_bg_color_preview\", previewMode)\n            );\n            for (const td of selectedTds) {\n                this.dependencies.color.colorElement(td, color, mode);\n                if (color) {\n                    td.style[\"color\"] = getComputedStyle(td).color;\n                } else {\n                    td.style[\"color\"] = \"\";\n                }\n            }\n        }\n    }\n\n    adjustTargetedNodes(targetedNodes) {\n        const modifiedTargetedNodes = [];\n        const visitedTables = new Set();\n        for (const node of targetedNodes) {\n            const selectedTable = closestElement(node, \".o_selected_table\");\n            if (selectedTable) {\n                if (visitedTables.has(selectedTable)) {\n                    continue;\n                }\n                visitedTables.add(selectedTable);\n                for (const selectedTd of selectedTable.querySelectorAll(\".o_selected_td\")) {\n                    modifiedTargetedNodes.push(selectedTd, ...descendants(selectedTd));\n                }\n            } else {\n                modifiedTargetedNodes.push(node);\n            }\n        }\n        return modifiedTargetedNodes;\n    }\n\n    resetTableSelection() {\n        const selection = this.dependencies.selection.getEditableSelection({ deep: true });\n        const anchorTD = closestElement(selection.anchorNode, \".o_selected_td\");\n        if (!anchorTD) {\n            return;\n        }\n        this.deselectTable();\n        this.dependencies.selection.setSelection({\n            anchorNode: anchorTD.firstChild,\n            anchorOffset: 0,\n            focusNode: anchorTD.lastChild,\n            focusOffset: nodeSize(anchorTD.lastChild),\n        });\n    }\n\n    /**\n     * @param {DocumentFragment} clonedContents\n     * @param {import(\"@html_editor/core/selection_plugin\").EditorSelection} selection\n     */\n    processContentForClipboard(clonedContents, selection) {\n        if (clonedContents.firstChild.nodeName === \"TR\" || isTableCell(clonedContents.firstChild)) {\n            // We enter this case only if selection is within single table.\n            const table = closestElement(selection.commonAncestorContainer, \"table\");\n            const tableClone = table.cloneNode(true);\n            // A table is considered fully selected if it is nested inside a\n            // cell that is itself selected, or if all its own cells are\n            // selected.\n            const isTableFullySelected =\n                (table.parentElement && !!closestElement(table.parentElement, \".o_selected_td\")) ||\n                getTableCells(table).every((td) => td.classList.contains(\"o_selected_td\"));\n            if (!isTableFullySelected) {\n                for (const td of tableClone.querySelectorAll(\":is(td, th):not(.o_selected_td)\")) {\n                    if (closestElement(td, \"table\") === tableClone) {\n                        // ignore nested\n                        td.remove();\n                    }\n                }\n                const trsWithoutTd = Array.from(tableClone.querySelectorAll(\"tr\")).filter(\n                    (row) => !row.querySelector(\"td, th\")\n                );\n                for (const tr of trsWithoutTd) {\n                    if (closestElement(tr, \"table\") === tableClone) {\n                        // ignore nested\n                        tr.remove();\n                    }\n                }\n            }\n            // If it is fully selected, clone the whole table rather than\n            // just its rows.\n            clonedContents = tableClone;\n        }\n        const startTable = closestElement(selection.startContainer, \"table\");\n        if (clonedContents.firstChild.nodeName === \"TABLE\" && startTable) {\n            // Make sure the full leading table is copied.\n            clonedContents.firstChild.after(startTable.cloneNode(true));\n            clonedContents.firstChild.remove();\n        }\n        const endTable = closestElement(selection.endContainer, \"table\");\n        if (clonedContents.lastChild.nodeName === \"TABLE\" && endTable) {\n            // Make sure the full trailing table is copied.\n            clonedContents.lastChild.before(endTable.cloneNode(true));\n            clonedContents.lastChild.remove();\n        }\n        this.deselectTable(clonedContents);\n        return clonedContents;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport {\n    closestElement,\n    getAdjacentNextSiblings,\n    getAdjacentPreviousSiblings,\n} from \"@html_editor/utils/dom_traversal\";\nimport { getColumnIndex } from \"@html_editor/utils/table\";\nimport { BORDER_SENSITIVITY } from \"@html_editor/main/table/table_plugin\";\nimport { isTableCell } from \"@html_editor/utils/dom_info\";\n\nexport class TableResizePlugin extends Plugin {\n    static id = \"tableResize\";\n    static dependencies = [\"table\", \"history\"];\n\n    setup() {\n        this.addDomListener(this.editable, \"dblclick\", this.fitToContent);\n        this.addDomListener(this.editable, \"mousedown\", this.onMousedown);\n        this.addDomListener(this.editable, \"mousemove\", this.onMousemove);\n    }\n\n    /**\n     * If the mouse is hovering over one of the borders of a table cell element,\n     * return the side of that border ('left'|'top'|'right'|'bottom').\n     * Otherwise, return false.\n     *\n     * @private\n     * @param {MouseEvent} ev\n     * @returns {string|boolean}\n     */\n    isHoveringTdBorder(ev) {\n        const target = /** @type {HTMLElement} */ (ev.target);\n        if (ev.target && isTableCell(target) && target.isContentEditable) {\n            const targetRect = target.getBoundingClientRect();\n            if (ev.clientX <= targetRect.x + BORDER_SENSITIVITY) {\n                return \"left\";\n            } else if (ev.clientY <= targetRect.y + BORDER_SENSITIVITY) {\n                return \"top\";\n            } else if (ev.clientX >= targetRect.x + target.clientWidth - BORDER_SENSITIVITY) {\n                return \"right\";\n            } else if (ev.clientY >= targetRect.y + target.clientHeight - BORDER_SENSITIVITY) {\n                return \"bottom\";\n            }\n        }\n        return false;\n    }\n    /**\n     * Change the cursor to a resizing cursor, in the direction specified. If no\n     * direction is specified, return the cursor to its default.\n     *\n     * @private\n     * @param {'col'|'row'|false} direction 'col'/'row' to hint column/row,\n     *                                      false to remove the hints\n     */\n    setTableResizeCursor(direction) {\n        const classList = this.editable.classList;\n        if (classList.contains(\"o_col_resize\")) {\n            classList.remove(\"o_col_resize\");\n        }\n        if (classList.contains(\"o_row_resize\")) {\n            classList.remove(\"o_row_resize\");\n        }\n        if (direction === \"col\") {\n            this.editable.classList.add(\"o_col_resize\");\n        } else if (direction === \"row\") {\n            this.editable.classList.add(\"o_row_resize\");\n        }\n    }\n\n    /**\n     * Resizes a table in the given direction, by \"pulling\" the border between\n     * the given targets (ordered left to right or top to bottom).\n     *\n     * @param {MouseEvent} ev\n     * @param {'col'|'row'} direction\n     * @param {HTMLElement} target1\n     * @param {HTMLElement} target2\n     */\n    resizeTable(ev, direction, target1, target2) {\n        ev.preventDefault();\n        const position = target1 ? (target2 ? \"middle\" : \"last\") : \"first\";\n        let [item, neighbor] = [target1 || target2, target2];\n        const table = closestElement(item, \"table\");\n        const [sizeProp, positionProp, clientPositionProp] =\n            direction === \"col\" ? [\"width\", \"x\", \"clientX\"] : [\"height\", \"y\", \"clientY\"];\n\n        const isRTL = this.config.direction === \"rtl\";\n        // Preserve current width.\n        if (sizeProp === \"width\") {\n            const tableRect = table.getBoundingClientRect();\n            table.style[sizeProp] = tableRect[sizeProp] + \"px\";\n        }\n        const unsizedItemsSelector = `${\n            direction === \"col\" ? \"td\" : \"tr\"\n        }:not([style*=${sizeProp}])`;\n        for (const unsizedItem of table.querySelectorAll(unsizedItemsSelector)) {\n            unsizedItem.style[sizeProp] = unsizedItem.getBoundingClientRect()[sizeProp] + \"px\";\n        }\n\n        // TD widths should only be applied in the first row. Change targets and\n        // clean the rest.\n        if (direction === \"col\") {\n            let hostCell = closestElement(table, isTableCell);\n            const hostCells = [];\n            while (hostCell) {\n                hostCells.push(hostCell);\n                hostCell = closestElement(hostCell.parentElement, isTableCell);\n            }\n            const nthColumn = getColumnIndex(item);\n            const firstRow = [...table.querySelector(\"tr\").children];\n            [item, neighbor] = [firstRow[nthColumn], firstRow[nthColumn + 1]];\n            for (const td of hostCells) {\n                if (\n                    td !== item &&\n                    td !== neighbor &&\n                    closestElement(td, \"table\") === table &&\n                    getColumnIndex(td) !== 0\n                ) {\n                    td.style.removeProperty(sizeProp);\n                }\n            }\n            if (isRTL && position == \"middle\") {\n                [item, neighbor] = [neighbor, item];\n            }\n        }\n\n        const MIN_SIZE = 33; // TODO: ideally, find this value programmatically.\n        switch (position) {\n            case \"first\": {\n                const marginProp =\n                    direction === \"col\" ? (isRTL ? \"marginRight\" : \"marginLeft\") : \"marginTop\";\n                const itemRect = item.getBoundingClientRect();\n                const tableStyle = getComputedStyle(table);\n                const currentMargin = parseFloat(tableStyle[marginProp]);\n                let sizeDelta = itemRect[positionProp] - ev[clientPositionProp];\n                if (direction === \"col\" && isRTL) {\n                    sizeDelta =\n                        ev[clientPositionProp] - itemRect[positionProp] - itemRect[sizeProp];\n                }\n                const newMargin = currentMargin - sizeDelta;\n                const currentSize = itemRect[sizeProp];\n                const newSize = currentSize + sizeDelta;\n                if (newMargin >= 0 && newSize > MIN_SIZE) {\n                    const tableRect = table.getBoundingClientRect();\n                    // Check if a nested table would overflow its parent cell.\n                    const hostCell = closestElement(table.parentElement, isTableCell);\n                    const childTable = item.querySelector(\"table\");\n                    const endProp = isRTL ? \"left\" : \"right\";\n                    if (\n                        direction === \"col\" &&\n                        ((hostCell &&\n                            tableRect[endProp] + sizeDelta >\n                                hostCell.getBoundingClientRect()[endProp] - 5) ||\n                            (childTable &&\n                                childTable.getBoundingClientRect()[endProp] >\n                                    itemRect[endProp] + sizeDelta - 5))\n                    ) {\n                        break;\n                    }\n                    table.style[marginProp] = newMargin + \"px\";\n                    item.style[sizeProp] = newSize + \"px\";\n                    if (sizeProp === \"width\") {\n                        table.style[sizeProp] = tableRect[sizeProp] + sizeDelta + \"px\";\n                    }\n                }\n                break;\n            }\n            case \"middle\": {\n                const [itemRect, neighborRect] = [\n                    item.getBoundingClientRect(),\n                    neighbor.getBoundingClientRect(),\n                ];\n                const [currentSize, newSize] = [\n                    itemRect[sizeProp],\n                    ev[clientPositionProp] - itemRect[positionProp],\n                ];\n                const editableStyle = getComputedStyle(this.editable);\n                const sizeDelta = newSize - currentSize;\n                const currentNeighborSize = neighborRect[sizeProp];\n                const newNeighborSize = currentNeighborSize - sizeDelta;\n                const enclosingCell = closestElement(table, \"td, th\");\n                const containerWidth =\n                    enclosingCell?.getBoundingClientRect().width || this.editable.clientWidth;\n                const maxWidth =\n                    containerWidth -\n                    parseFloat(editableStyle.paddingLeft) -\n                    parseFloat(editableStyle.paddingRight);\n                const tableRect = table.getBoundingClientRect();\n                if (\n                    newSize > MIN_SIZE &&\n                    // prevent resizing horizontally beyond the bounds of\n                    // the editable:\n                    (direction === \"row\" ||\n                        newNeighborSize > MIN_SIZE ||\n                        tableRect[sizeProp] + sizeDelta < maxWidth)\n                ) {\n                    // Check if a nested table would overflow its parent cell.\n                    const childTable = item.querySelector(\"table\");\n                    if (\n                        direction === \"col\" &&\n                        childTable &&\n                        childTable.getBoundingClientRect().right > itemRect.right + sizeDelta - 5\n                    ) {\n                        break;\n                    }\n                    item.style[sizeProp] = newSize + \"px\";\n                    if (direction === \"col\") {\n                        neighbor.style[sizeProp] =\n                            (newNeighborSize > MIN_SIZE ? newNeighborSize : currentNeighborSize) +\n                            \"px\";\n                    } else if (sizeProp === \"width\") {\n                        table.style[sizeProp] = tableRect[sizeProp] + sizeDelta + \"px\";\n                    }\n                }\n                break;\n            }\n            case \"last\": {\n                const itemRect = item.getBoundingClientRect();\n                let sizeDelta =\n                    ev[clientPositionProp] - (itemRect[positionProp] + itemRect[sizeProp]); // todo: rephrase\n                if (direction === \"col\" && isRTL) {\n                    sizeDelta = itemRect[positionProp] - ev[clientPositionProp];\n                }\n                const currentSize = itemRect[sizeProp];\n                const newSize = currentSize + sizeDelta;\n                if ((newSize >= 0 || direction === \"row\") && newSize > MIN_SIZE) {\n                    const tableRect = table.getBoundingClientRect();\n                    // Check if a nested table would overflow its parent cell.\n                    const hostCell = closestElement(table.parentElement, isTableCell);\n                    const childTable = item.querySelector(\"table\");\n                    const endProp = isRTL ? \"left\" : \"right\";\n                    if (\n                        direction === \"col\" &&\n                        ((hostCell &&\n                            tableRect[endProp] + sizeDelta >\n                                hostCell.getBoundingClientRect()[endProp] - 5) ||\n                            (childTable &&\n                                childTable.getBoundingClientRect()[endProp] >\n                                    itemRect[endProp] + sizeDelta - 5))\n                    ) {\n                        break;\n                    }\n                    if (sizeProp === \"width\") {\n                        table.style[sizeProp] = tableRect[sizeProp] + sizeDelta + \"px\";\n                    }\n                    item.style[sizeProp] = newSize + \"px\";\n                }\n                break;\n            }\n        }\n    }\n\n    /**\n     * Resizes rows and columns based on the mouse's double-click on the borders.\n     * Adjusts width of columns or height of rows depending on the cursor position.\n     * Adjacent rows/columns are resized as well.\n     *\n     * @param {MouseEvent} ev - The double-click mouse event.\n     */\n    fitToContent(ev) {\n        const isHoveringTdBorder = this.isHoveringTdBorder(ev);\n        if (!isHoveringTdBorder) {\n            return;\n        }\n        const cell = ev.target;\n        if ([\"left\", \"right\"].includes(isHoveringTdBorder)) {\n            const table = closestElement(cell, \"table\");\n            const currentColumnIndex = getColumnIndex(cell);\n            const currentColumnCells = table.querySelectorAll(\n                `tr :is(td, th):nth-of-type(${currentColumnIndex + 1})`\n            );\n            this.dependencies.table.resetColumnWidth(currentColumnCells[0]);\n            const isLeftSideClick = isHoveringTdBorder === \"left\";\n            if (\n                (isLeftSideClick && currentColumnIndex > 0) ||\n                (!isLeftSideClick && currentColumnIndex < table.rows[0].cells.length - 1)\n            ) {\n                const siblingColumnIndex = isLeftSideClick\n                    ? currentColumnIndex - 1\n                    : currentColumnIndex + 1;\n                const siblingColumnCells = table.querySelectorAll(\n                    `tr :is(td, th):nth-of-type(${siblingColumnIndex + 1})`\n                );\n                this.dependencies.table.resetColumnWidth(siblingColumnCells[0]);\n            }\n        } else if ([\"top\", \"bottom\"].includes(isHoveringTdBorder)) {\n            const currentRow = cell.parentElement;\n            this.dependencies.table.resetRowHeight(currentRow);\n            const siblingRow =\n                isHoveringTdBorder === \"top\"\n                    ? currentRow.previousElementSibling\n                    : currentRow.nextElementSibling;\n            if (siblingRow) {\n                this.dependencies.table.resetRowHeight(siblingRow);\n            }\n        }\n    }\n\n    onMousedown(ev) {\n        const isHoveringTdBorder = this.isHoveringTdBorder(ev);\n        const isRTL = this.config.direction === \"rtl\";\n        if (isHoveringTdBorder) {\n            ev.preventDefault();\n            const direction =\n                { top: \"row\", right: \"col\", bottom: \"row\", left: \"col\" }[isHoveringTdBorder] ||\n                false;\n            let target1, target2;\n            const column = closestElement(ev.target, \"tr\");\n            if (isHoveringTdBorder === \"top\" && column) {\n                target1 = getAdjacentPreviousSiblings(column).find(\n                    (node) => node.nodeName === \"TR\"\n                );\n                target2 = closestElement(ev.target, \"tr\");\n            } else if (isHoveringTdBorder === \"right\") {\n                if (isRTL) {\n                    target1 = getAdjacentPreviousSiblings(ev.target).find(isTableCell);\n                    target2 = ev.target;\n                } else {\n                    target1 = ev.target;\n                    target2 = getAdjacentNextSiblings(ev.target).find(isTableCell);\n                }\n            } else if (isHoveringTdBorder === \"bottom\" && column) {\n                target1 = closestElement(ev.target, \"tr\");\n                target2 = getAdjacentNextSiblings(column).find((node) => node.nodeName === \"TR\");\n            } else if (isHoveringTdBorder === \"left\") {\n                if (isRTL) {\n                    target1 = ev.target;\n                    target2 = getAdjacentNextSiblings(ev.target).find(isTableCell);\n                } else {\n                    target1 = getAdjacentPreviousSiblings(ev.target).find(isTableCell);\n                    target2 = ev.target;\n                }\n            }\n            this.isResizingTable = true;\n            this.setTableResizeCursor(direction);\n            const resizeTable = (ev) => this.resizeTable(ev, direction, target1, target2);\n            const stopResizing = (ev) => {\n                ev.preventDefault();\n                this.isResizingTable = false;\n                this.setTableResizeCursor(false);\n                this.dependencies.history.addStep();\n                this.document.removeEventListener(\"mousemove\", resizeTable);\n                this.document.removeEventListener(\"mouseup\", stopResizing);\n                this.document.removeEventListener(\"mouseleave\", stopResizing);\n            };\n            this.document.addEventListener(\"mousemove\", resizeTable);\n            this.document.addEventListener(\"mouseup\", stopResizing);\n            this.document.addEventListener(\"mouseleave\", stopResizing);\n        }\n    }\n    onMousemove(ev) {\n        const direction =\n            { top: \"row\", right: \"col\", bottom: \"row\", left: \"col\" }[this.isHoveringTdBorder(ev)] ||\n            false;\n        if (direction || !this.isResizingTable) {\n            this.setTableResizeCursor(direction);\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { reactive } from \"@odoo/owl\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { TableMenu } from \"./table_menu\";\nimport { TablePicker } from \"./table_picker\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\n/**\n * This plugin only contains the table ui feature (table picker, menus, ...).\n * All actual table manipulation code is located in the table plugin.\n */\nexport class TableUIPlugin extends Plugin {\n    static id = \"tableUi\";\n    static dependencies = [\"history\", \"overlay\", \"table\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"openTablePicker\",\n                title: _t(\"Table\"),\n                description: _t(\"Insert a table\"),\n                icon: \"fa-table\",\n                run: this.openPickerOrInsertTable.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        powerbox_items: [\n            {\n                categoryId: \"structure\",\n                commandId: \"openTablePicker\",\n            },\n        ],\n    };\n\n    setup() {\n        /** @type {import(\"@html_editor/core/overlay_plugin\").Overlay} */\n        this.picker = this.dependencies.overlay.createOverlay(TablePicker, {\n            positionOptions: {\n                updatePositionOnResize: false,\n                onPositioned: (picker, position) => {\n                    const popperRect = picker.getBoundingClientRect();\n                    const { left } = position;\n                    if (this.config.direction === \"rtl\") {\n                        // position from the right instead of the left as it is needed\n                        // to ensure the expand animation is properly done\n                        picker.style.right = `${window.innerWidth - left - popperRect.width}px`;\n                        picker.style.removeProperty(\"left\");\n                    }\n                },\n            },\n        });\n\n        this.activeTd = null;\n\n        /** @type {import(\"@html_editor/core/overlay_plugin\").Overlay} */\n        this.colMenu = this.dependencies.overlay.createOverlay(TableMenu, {\n            positionOptions: {\n                position: \"top-fit\",\n                flip: false,\n            },\n        });\n        /** @type {import(\"@html_editor/core/overlay_plugin\").Overlay} */\n        this.rowMenu = this.dependencies.overlay.createOverlay(TableMenu, {\n            positionOptions: {\n                position: \"left-fit\",\n            },\n        });\n        this.addDomListener(this.document, \"pointermove\", this.onMouseMove);\n        const closeMenus = () => {\n            if (this.isMenuOpened) {\n                this.isMenuOpened = false;\n                this.colMenu.close();\n                this.rowMenu.close();\n            }\n        };\n        this.addDomListener(this.document, \"scroll\", closeMenus, true);\n    }\n\n    openPicker() {\n        this.picker.open({\n            props: {\n                editable: this.editable,\n                overlay: this.picker,\n                direction: this.config.direction || \"ltr\",\n                insertTable: (params) => this.dependencies.table.insertTable(params),\n            },\n        });\n    }\n\n    openPickerOrInsertTable() {\n        if (this.services.ui.isSmall) {\n            this.dependencies.table.insertTable({ cols: 3, rows: 3 });\n        } else {\n            this.openPicker();\n        }\n    }\n\n    onMouseMove(ev) {\n        const target = ev.target;\n        if (this.isMenuOpened) {\n            return;\n        }\n        if (\n            [\"TD\", \"TH\"].includes(target.tagName) &&\n            target !== this.activeTd &&\n            this.editable.contains(target)\n        ) {\n            if (ev.target.isContentEditable && closestElement(target, \"table\").isContentEditable) {\n                this.setActiveTd(target);\n            }\n        } else if (this.activeTd) {\n            const isOverlay = target.closest(\".o-overlay-container\");\n            if (isOverlay) {\n                return;\n            }\n            const parentTd = closestElement(target, \"td, th\");\n            if (!parentTd) {\n                this.setActiveTd(null);\n            }\n        }\n    }\n\n    createDropdownState(menuToClose) {\n        const dropdownState = reactive({\n            isOpen: false,\n            open: () => {\n                dropdownState.isOpen = true;\n                menuToClose.close();\n                this.isMenuOpened = true;\n            },\n            close: () => {\n                dropdownState.isOpen = false;\n                this.isMenuOpened = false;\n            },\n        });\n        return dropdownState;\n    }\n\n    setActiveTd(td) {\n        this.activeTd = td;\n        this.colMenu.close();\n        this.rowMenu.close();\n        if (!td) {\n            return;\n        }\n        const withAddStep =\n            (fn) =>\n            (...args) => {\n                fn(...args);\n                this.dependencies.history.addStep();\n            };\n        const tableMethods = {\n            moveColumn: withAddStep(this.dependencies.table.moveColumn),\n            addColumn: withAddStep(this.dependencies.table.addColumn),\n            removeColumn: withAddStep(this.dependencies.table.removeColumn),\n            moveRow: withAddStep(this.dependencies.table.moveRow),\n            addRow: withAddStep(this.dependencies.table.addRow),\n            removeRow: withAddStep(this.dependencies.table.removeRow),\n            turnIntoHeader: withAddStep(this.dependencies.table.turnIntoHeader),\n            turnIntoRow: withAddStep(this.dependencies.table.turnIntoRow),\n            resetRowHeight: withAddStep(this.dependencies.table.resetRowHeight),\n            resetColumnWidth: withAddStep(this.dependencies.table.resetColumnWidth),\n            resetTableSize: withAddStep(this.dependencies.table.resetTableSize),\n            clearColumnContent: withAddStep(this.dependencies.table.clearColumnContent),\n            clearRowContent: withAddStep(this.dependencies.table.clearRowContent),\n        };\n        if (td.cellIndex === 0) {\n            this.rowMenu.open({\n                target: td,\n                props: {\n                    type: \"row\",\n                    overlay: this.rowMenu,\n                    target: td,\n                    dropdownState: this.createDropdownState(this.colMenu),\n                    ...tableMethods,\n                },\n            });\n        }\n        if (td.parentElement.rowIndex === 0) {\n            this.colMenu.open({\n                target: td,\n                props: {\n                    type: \"column\",\n                    overlay: this.colMenu,\n                    target: td,\n                    dropdownState: this.createDropdownState(this.rowMenu),\n                    direction: this.config.direction || \"ltr\",\n                    ...tableMethods,\n                },\n            });\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestBlock, isBlock } from \"@html_editor/utils/blocks\";\nimport { splitTextNode } from \"@html_editor/utils/dom\";\nimport { isEditorTab, isTextNode, isZWS } from \"@html_editor/utils/dom_info\";\nimport {\n    descendants,\n    getAdjacentPreviousSiblings,\n    closestElement,\n    firstLeaf,\n    selectElements,\n} from \"@html_editor/utils/dom_traversal\";\nimport { parseHTML } from \"@html_editor/utils/html\";\nimport { DIRECTIONS, childNodeIndex } from \"@html_editor/utils/position\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nconst tabHtml = '<span class=\"oe-tabs\" contenteditable=\"false\">\\u0009</span>\\u200B';\nconst GRID_COLUMN_WIDTH = 40; //@todo Configurable?\n\n/**\n * Checks if the given tab element represents an indentation.\n * An indentation tab is one that is not preceded by visible text.\n *\n * @param {HTMLElement} tab - The tab element to check.\n * @returns {boolean} - True if the tab represents an indentation, false otherwise.\n */\nfunction isIndentationTab(tab) {\n    return !getAdjacentPreviousSiblings(tab).some(\n        (sibling) => isTextNode(sibling) && !/^[\\u200B\\s]*$/.test(sibling.textContent)\n    );\n}\n\n/**\n * @typedef { Object } TabulationShared\n * @property { TabulationPlugin['indentBlocks'] } indentBlocks\n * @property { TabulationPlugin['outdentBlocks'] } outdentBlocks\n */\n\n/**\n * @typedef {(() => void | true)[]} shift_tab_overrides\n * @typedef {(() => void | true)[]} tab_overrides\n */\n\nexport class TabulationPlugin extends Plugin {\n    static id = \"tabulation\";\n    static dependencies = [\"dom\", \"selection\", \"history\", \"delete\"];\n    static shared = [\"indentBlocks\", \"outdentBlocks\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"tab\",\n                run: this.handleTab.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n            {\n                id: \"shiftTab\",\n                run: this.handleShiftTab.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        shortcuts: [\n            { hotkey: \"tab\", commandId: \"tab\" },\n            { hotkey: \"shift+tab\", commandId: \"shiftTab\" },\n        ],\n        content_not_editable_providers: (rootEl) => [...selectElements(rootEl, \".oe-tabs\")],\n        contenteditable_to_remove_selector: \"span.oe-tabs\",\n\n        /** Handlers */\n        normalize_handlers: this.normalize.bind(this),\n\n        /** Overrides */\n        delete_forward_overrides: this.handleDeleteForward.bind(this),\n\n        unsplittable_node_predicates: isEditorTab, // avoid merge\n    };\n\n    handleTab() {\n        if (this.delegateTo(\"tab_overrides\")) {\n            return;\n        }\n\n        const selection = this.dependencies.selection.getEditableSelection();\n        if (selection.isCollapsed) {\n            this.insertTab();\n        } else {\n            const targetedBlocks = this.dependencies.selection.getTargetedBlocks();\n            this.indentBlocks(targetedBlocks);\n        }\n        this.dependencies.history.addStep();\n    }\n\n    handleShiftTab() {\n        if (this.delegateTo(\"shift_tab_overrides\")) {\n            return;\n        }\n        const targetedBlocks = this.dependencies.selection.getTargetedBlocks();\n        this.outdentBlocks(targetedBlocks);\n        this.dependencies.history.addStep();\n    }\n\n    insertTab() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const element = closestElement(selection.anchorNode);\n        const isSelectionAtStart =\n            firstLeaf(element) === selection.anchorNode &&\n            (selection.anchorOffset === 0 || element.textContent === \"\\u200B\");\n        const tab = parseHTML(this.document, tabHtml);\n        if (isSelectionAtStart && !isBlock(element)) {\n            element.before(tab);\n        } else {\n            this.dependencies.dom.insert(tab);\n        }\n    }\n\n    /**\n     * @param {HTMLElement} blocks\n     */\n    indentBlocks(blocks) {\n        const selectionToRestore = this.dependencies.selection.getEditableSelection();\n        const tab = parseHTML(this.document, tabHtml);\n        for (const block of blocks) {\n            block.prepend(tab.cloneNode(true));\n        }\n        this.dependencies.selection.setSelection(selectionToRestore, { normalize: false });\n    }\n\n    /**\n     * @param {HTMLElement} blocks\n     */\n    outdentBlocks(blocks) {\n        for (const block of blocks) {\n            const firstTab = descendants(block).find(isEditorTab);\n            if (firstTab && isIndentationTab(firstTab)) {\n                this.removeTrailingZWS(firstTab);\n                firstTab.remove();\n            }\n        }\n    }\n\n    removeTrailingZWS(tab) {\n        const selection = this.dependencies.selection.getEditableSelection();\n        const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;\n        const updateAnchor = anchorNode === tab.nextSibling;\n        const updateFocus = focusNode === tab.nextSibling;\n        let zwsRemoved = 0;\n        while (\n            tab.nextSibling &&\n            tab.nextSibling.nodeType === Node.TEXT_NODE &&\n            tab.nextSibling.textContent.startsWith(\"\\u200B\")\n        ) {\n            splitTextNode(tab.nextSibling, 1, DIRECTIONS.LEFT);\n            tab.nextSibling.remove();\n            zwsRemoved++;\n        }\n        if (updateAnchor || updateFocus) {\n            this.dependencies.selection.setSelection({\n                anchorNode: updateAnchor ? tab.nextSibling : anchorNode,\n                anchorOffset: updateAnchor ? Math.max(0, anchorOffset - zwsRemoved) : anchorOffset,\n                focusNode: updateFocus ? tab.nextSibling : focusNode,\n                focusOffset: updateFocus ? Math.max(0, focusOffset - zwsRemoved) : focusOffset,\n            });\n        }\n    }\n\n    /**\n     * @param {HTMLSpanElement} tabSpan - span.oe-tabs element\n     */\n    adjustTabWidth(tabSpan) {\n        let tabPreviousSibling = tabSpan.previousSibling;\n        while (isZWS(tabPreviousSibling)) {\n            tabPreviousSibling = tabPreviousSibling.previousSibling;\n        }\n        if (isEditorTab(tabPreviousSibling)) {\n            tabSpan.style.width = `${GRID_COLUMN_WIDTH}px`;\n            return;\n        }\n        const spanRect = tabSpan.getBoundingClientRect();\n        const referenceRect = this.editable.firstElementChild?.getBoundingClientRect();\n        // @ todo @phoenix Re-evaluate if this check is necessary.\n        // Values from getBoundingClientRect() are all zeros during\n        // Editor startup or saving. We cannot recalculate the tabs\n        // width in thoses cases.\n        if (!referenceRect?.width || !spanRect.width) {\n            return;\n        }\n        const relativePosition = spanRect.left - referenceRect.left;\n        const distToNextGridLine = GRID_COLUMN_WIDTH - (relativePosition % GRID_COLUMN_WIDTH);\n        // Round to the first decimal point.\n        const width = distToNextGridLine.toFixed(1);\n        tabSpan.style.width = `${width}px`;\n    }\n\n    /**\n     * Aligns the tabs under the specified tree to a grid.\n     *\n     * @param {HTMLElement} [root] - The tree root.\n     */\n    alignTabs(root = this.editable) {\n        const block = closestBlock(root);\n        if (!block) {\n            return;\n        }\n        for (const tab of block.querySelectorAll(\"span.oe-tabs\")) {\n            this.adjustTabWidth(tab);\n        }\n    }\n\n    // When deleting an editor tab, we need to ensure it's related\n    // ZWS will deleted as well.\n    // @todo @phoenix: for some reason, there might be more than one ZWS.\n    // Investigate why.\n    expandRangeToIncludeZWS(tabElement) {\n        let previous = tabElement;\n        let node = tabElement.nextSibling;\n        while (node?.nodeType === Node.TEXT_NODE) {\n            for (let i = 0; i < node.textContent.length; i++) {\n                if (node.textContent[i] !== \"\\u200B\") {\n                    return [node, i];\n                }\n            }\n            previous = node;\n            node = node.nextSibling;\n        }\n        return [previous.parentElement, childNodeIndex(previous) + 1];\n    }\n\n    // @todo consider registering this as adjustRange callback instead.\n    handleDeleteForward(range) {\n        let { endContainer, endOffset } = range;\n        if (!(endContainer?.nodeType === Node.ELEMENT_NODE) || !endOffset) {\n            return;\n        }\n        const nodeToDelete = endContainer.childNodes[endOffset - 1];\n        if (isEditorTab(nodeToDelete)) {\n            [endContainer, endOffset] = this.expandRangeToIncludeZWS(nodeToDelete);\n            range = this.dependencies.delete.deleteRange({ ...range, endContainer, endOffset });\n            this.dependencies.selection.setSelection({\n                anchorNode: range.startContainer,\n                anchorOffset: range.startOffset,\n            });\n            return true;\n        }\n    }\n    normalize(el) {\n        this.alignTabs(el);\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { Plugin } from \"../plugin\";\nimport { closestBlock } from \"../utils/blocks\";\nimport { closestElement } from \"../utils/dom_traversal\";\nimport { isContentEditable, isTextNode } from \"@html_editor/utils/dom_info\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport class TextDirectionPlugin extends Plugin {\n    static id = \"textDirection\";\n    static dependencies = [\"selection\", \"history\", \"split\", \"format\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"switchDirection\",\n                title: _t(\"Switch direction\"),\n                description: _t(\"Switch the text's direction\"),\n                icon: \"fa-exchange\",\n                run: this.switchDirection.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        powerbox_items: [\n            {\n                categoryId: \"format\",\n                commandId: \"switchDirection\",\n            },\n        ],\n    };\n\n    setup() {\n        if (this.config.direction) {\n            this.editable.setAttribute(\"dir\", this.config.direction);\n        }\n        this.direction = this.config.direction || \"ltr\";\n    }\n\n    switchDirection() {\n        const selection = this.dependencies.split.splitSelection();\n        const targetedTextNodes = [\n            selection.anchorNode,\n            ...this.dependencies.selection.getTargetedNodes(),\n        ].filter((n) => isTextNode(n) && isContentEditable(n) && n.nodeValue.trim().length);\n        const blocks = new Set(\n            targetedTextNodes.map(\n                (textNode) =>\n                    closestElement(textNode, \"ul,ol\") ||\n                    closestElement(textNode, \"[data-embedded='toggleBlock']\") ||\n                    closestBlock(textNode)\n            )\n        );\n\n        const shouldApplyStyle = !this.dependencies.format.isSelectionFormat(\"switchDirection\");\n\n        for (const block of blocks) {\n            for (const node of block.querySelectorAll(\"ul,ol\")) {\n                blocks.add(node);\n            }\n        }\n        for (const block of blocks) {\n            if (!shouldApplyStyle) {\n                block.removeAttribute(\"dir\");\n            } else {\n                block.setAttribute(\"dir\", this.direction === \"ltr\" ? \"rtl\" : \"ltr\");\n            }\n        }\n\n        for (const element of blocks) {\n            const style = getComputedStyle(element);\n            if (style.direction === \"ltr\" && style.textAlign === \"right\") {\n                element.style.setProperty(\"text-align\", \"left\");\n            } else if (style.direction === \"rtl\" && style.textAlign === \"left\") {\n                element.style.setProperty(\"text-align\", \"right\");\n            }\n        }\n        this.dependencies.history.addStep();\n    }\n}\n", "import { Component, onMounted, useExternalListener, useRef } from \"@odoo/owl\";\nimport { Toolbar } from \"./toolbar\";\n\nexport class ToolbarMobile extends Component {\n    static template = \"html_editor.MobileToolbar\";\n    static props = [\"*\"];\n    static components = {\n        Toolbar,\n    };\n\n    setup() {\n        this.toolbar = useRef(\"toolbarWrapper\");\n        useExternalListener(window.visualViewport, \"resize\", this.fixToolbarPosition);\n        useExternalListener(window.visualViewport, \"scroll\", this.fixToolbarPosition);\n        onMounted(() => {\n            this.fixToolbarPosition();\n        });\n    }\n\n    /**\n     * Fixes the position of the toolbar for the keyboard height.\n     */\n    fixToolbarPosition() {\n        const keyboardHeight =\n            window.innerHeight - (window.visualViewport.height + window.visualViewport.offsetTop);\n        if (keyboardHeight > 0) {\n            this.toolbar.el.style.bottom = `${keyboardHeight}px`;\n        } else {\n            this.toolbar.el.style.bottom = `0px`;\n        }\n    }\n}\n", "import { Component, useState, validate } from \"@odoo/owl\";\nimport { omit, pick } from \"@web/core/utils/objects\";\n\nexport class Toolbar extends Component {\n    static template = \"html_editor.Toolbar\";\n    static props = {\n        class: { type: String, optional: true },\n        getSelection: Function,\n        focusEditable: Function,\n        state: {\n            type: Object,\n            shape: {\n                namespace: { type: String, optional: true },\n                buttonGroups: {\n                    type: Array,\n                    element: {\n                        type: Object,\n                        shape: {\n                            id: String,\n                            buttons: {\n                                type: Array,\n                                element: {\n                                    type: Object,\n                                    validate: (button) => {\n                                        const base = {\n                                            id: String,\n                                            description: String,\n                                            isDisabled: Boolean,\n                                        };\n                                        if (button.Component) {\n                                            validate(button, {\n                                                ...base,\n                                                Component: Function,\n                                                props: { type: Object, optional: true },\n                                            });\n                                        } else {\n                                            validate(button, {\n                                                ...base,\n                                                run: Function,\n                                                icon: { type: String, optional: true },\n                                                text: { type: String, optional: true },\n                                                isActive: Boolean,\n                                            });\n                                        }\n                                        return true;\n                                    },\n                                },\n                            },\n                        },\n                    },\n                },\n            },\n        },\n    };\n\n    setup() {\n        this.state = useState(this.props.state);\n    }\n\n    onButtonClick(button) {\n        button.run();\n        this.props.focusEditable();\n    }\n}\n\nexport const toolbarButtonProps = {\n    title: [String, Function],\n    getSelection: Function,\n    isDisabled: Boolean,\n};\n\n/** @typedef {import(\"@html_editor/core/user_command_plugin\").UserCommand} UserCommand */\n/** @typedef {import(\"./toolbar_plugin\").ToolbarCommandItem} ToolbarCommandItem */\n/** @typedef {import(\"./toolbar_plugin\").ToolbarCommandButton} ToolbarCommandButton */\n\n/**\n * @param {UserCommand} userCommand\n * @param {ToolbarCommandItem} toolbarItem\n * @returns {ToolbarCommandButton}\n */\nexport function composeToolbarButton(userCommand, toolbarItem) {\n    const description = toolbarItem.description || userCommand.description;\n    return {\n        ...pick(userCommand, \"icon\"),\n        ...omit(toolbarItem, \"commandId\", \"commandParams\"),\n        run: () => userCommand.run(toolbarItem.commandParams),\n        isAvailable: (selection) =>\n            [userCommand.isAvailable, toolbarItem.isAvailable]\n                .filter(Boolean)\n                .every((predicate) => predicate(selection)),\n        description: description instanceof Function ? description : () => description,\n    };\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { isEmptyTextNode, isZWS } from \"@html_editor/utils/dom_info\";\nimport { reactive } from \"@odoo/owl\";\nimport { composeToolbarButton, Toolbar } from \"./toolbar\";\nimport { hasTouch } from \"@web/core/browser/feature_detection\";\nimport { registry } from \"@web/core/registry\";\nimport { ToolbarMobile } from \"./mobile_toolbar\";\nimport { debounce } from \"@web/core/utils/timing\";\nimport { omit, pick } from \"@web/core/utils/objects\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\n\n/** @typedef { import(\"@html_editor/core/selection_plugin\").EditorSelection } EditorSelection */\n/** @typedef {import(\"@html_editor/core/selection_plugin\").SelectionData} SelectionData */\n/** @typedef { import(\"@html_editor/core/user_command_plugin\").UserCommand } UserCommand */\n/** @typedef { import(\"@web/core/l10n/translation.js\")._t} _t */\n/** @typedef { ReturnType<_t> } TranslatedString */\n/** @typedef { (selection: EditorSelection, nodes: Node[]) => TranslatedString } TranslatedStringGetter */\n\n/**\n * @typedef {Object} ToolbarNamespace\n * @property {string} id\n * @property {(targetedNodes: Node[]) => boolean} isApplied\n *\n *\n * @typedef {Object} ToolbarGroup\n * @property {string} id\n * @property {string[]} [namespaces]\n *\n *\n * @typedef {ToolbarCommandItem | ToolbarComponentItem} ToolbarItem\n *\n * @typedef {Object} ToolbarCommandItem\n * Regular button: derives from a user command specified by commandId.\n * The properties maked with * can be omitted if they are present in the user command.\n * The ones marked with ?* are both optional and derivable from the user command.\n * @property {string} id\n * @property {string} groupId Id of a toolbar group\n * @property {string} commandId\n * @property {string[]} [namespaces]\n * @property {Object} [commandParams] Passed to the command's `run` function\n * @property {TranslatedString | TranslatedStringGetter} [description] * - becomes the button's title (and tooltip content)\n * @property {string} [icon] *\n * @property {string} [text] Can be used with (or instead of) `icon`\n * @property {(selection: EditorSelection) => boolean} [isAvailable] ? *\n * @property {(selection: EditorSelection, nodes: Node[]) => boolean} [isActive]\n * @property {(selection: EditorSelection, nodes: Node[]) => boolean} [isDisabled]\n *\n * @typedef {Object} ToolbarComponentItem\n * Adds a custom component to the toolbar.\n * @property {string} id\n * @property {string} groupId\n * @property {string[]} [namespaces]\n * @property {TranslatedString | TranslatedStringGetter} [description]\n * @property {Function} Component\n * @property {Object} props\n * @property {(selection: EditorSelection) => boolean} [isAvailable]\n *\n * ToolbarItem.id maps to the button's `name` attribute\n * ToolbarItem.description maps to the button's `title` attribute (tooltip content)\n */\n\n/**\n * Types after conversion to renderable toolbar buttons:\n *\n * @typedef {Object} ToolbarCommandButton\n * @property {string} id\n * @property {string} groupId\n * @property {TranslatedStringGetter} description\n * @property {Function} run\n * @property {string} [icon]\n * @property {string} [text]\n * @property {(selection: EditorSelection) => boolean} isAvailable\n * @property {(selection: EditorSelection, nodes: Node[]) => boolean} [isActive]\n * @property {(selection: EditorSelection, nodes: Node[]) => boolean} [isDisabled]\n *\n * @typedef {Object} ToolbarComponentButton\n * Adds a custom component to the toolbar (processed version with required fields).\n * @property {string} id\n * @property {string} groupId\n * @property {string[]} [namespaces]\n * @property {TranslatedStringGetter} description\n * @property {Function} Component\n * @property {Object} props\n * @property {(selection: EditorSelection) => boolean} isAvailable\n *\n * @typedef {ToolbarCommandButton | ToolbarComponentButton} ToolbarButton\n */\n\n/** Delay in ms for toolbar open after keyup, double click or triple click. */\nconst DELAY_TOOLBAR_OPEN = 300;\n/** Number of buttons below which toolbar will open directly in its expanded form */\nconst MIN_SIZE_FOR_COMPACT = 7;\n/** Special namespace that prevents the toolbar from opening */\nexport const DISABLED_NAMESPACE = \"disabled\";\n\n/**\n * @typedef { Object } ToolbarShared\n * @property { ToolbarPlugin['getToolbarInfo'] } getToolbarInfo\n */\n\n/**\n * @typedef {((namespace: string) => boolean)[]} can_display_toolbar\n * @typedef {((selectionData: SelectionData) => boolean)[]} collapsed_selection_toolbar_predicate\n *\n * @typedef {ToolbarGroup[]} toolbar_groups\n * @typedef {ToolbarNamespace[]} toolbar_namespaces\n */\n\n/**\n * @see UserCommand\n * @typedef {(ToolbarCommandItem | ToolbarComponentItem)[]} toolbar_items\n *\n * A ToolbarCommandItem must derive from a user command (see UserCommand)\n * specified by commandId. Properties defined in a toolbar item override those\n * from a user command.\n *\n * Example:\n *\n *     resources = {\n *         // see UserCommand\n *         user_commands: [\n *             {\n *                 id: myCommand,\n *                 run: myCommandFunction,\n *                 description: _t(\"My Command\"),\n *                 icon: \"fa-bug\",\n *             },\n *         ],\n *         // see ToolbarGroup\n *         toolbar_groups: [\n *             { id: \"myGroup\" },\n *         ],\n *         toolbar_items: [\n *             // See ToolbarCommandItem\n *             {\n *                 id: \"myButton\",\n *                 groupId: \"myGroup\",\n *                 commandId: \"myCommand\",\n *                 description: _t(\"My Toolbar Command Button\"), // overrides the user command's `description`\n *                 // `icon` is inferred from the user command\n *             },\n *             // See ToolbarComponentItem\n *             {\n *                 id: \"myComponentButton\",\n *                 groupId: \"myGroup\",\n *                 description: _t(\"My Toolbar Component Button\"),\n *                 Component: MyComponent,\n *                 props: { myProp: \"myValue\" },\n *             },\n *         ],\n *     };\n */\n\nexport class ToolbarPlugin extends Plugin {\n    static id = \"toolbar\";\n    static dependencies = [\"overlay\", \"selection\", \"userCommand\"];\n    static shared = [\"getToolbarInfo\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        selectionchange_handlers: this.handleSelectionChange.bind(this),\n        selection_leave_handlers: () => this.closeToolbar(),\n        step_added_handlers: () => this.updateToolbar(),\n        user_commands: {\n            id: \"expandToolbar\",\n            run: () => {\n                this.isToolbarExpanded = true;\n                this.updateToolbar();\n            },\n        },\n        toolbar_groups: [\n            withSequence(100, { id: \"expand_toolbar\", namespaces: [\"compact\"] }),\n            withSequence(30, { id: \"layout\" }),\n        ],\n        toolbar_items: {\n            id: \"expand_toolbar\",\n            groupId: \"expand_toolbar\",\n            commandId: \"expandToolbar\",\n            description: _t(\"Expand toolbar\"),\n            icon: \"oi-ellipsis-v\",\n        },\n        toolbar_namespace_providers: [\n            withSequence(100, (targetedNodes, editableSelection) =>\n                this.isToolbarVisible(targetedNodes, editableSelection) ? \"compact\" : undefined\n            ),\n        ],\n    };\n\n    setup() {\n        const groupIds = new Set();\n        for (const group of this.getResource(\"toolbar_groups\")) {\n            if (groupIds.has(group.id)) {\n                throw new Error(`Duplicate toolbar group id: ${group.id}`);\n            }\n            groupIds.add(group.id);\n        }\n        this.buttonGroups = this.getButtonGroups();\n        this.buttonsByNamespace = { DISABLED_NAMESPACE: [] };\n\n        this.isMobileToolbar = hasTouch() && window.visualViewport;\n\n        if (this.isMobileToolbar) {\n            this.overlay = new MobileToolbarOverlay(this.editable);\n        } else {\n            this.overlay = this.dependencies.overlay.createOverlay(Toolbar, {\n                positionOptions: {\n                    position: \"top-start\",\n                },\n                closeOnPointerdown: false,\n            });\n        }\n        this.state = reactive({ buttonGroups: [], namespace: undefined });\n\n        this.onSelectionChangeActive = true;\n        this.debouncedUpdateToolbar = debounce(this._updateToolbar, DELAY_TOOLBAR_OPEN);\n\n        if (this.isMobileToolbar) {\n            this.addDomListener(this.editable, \"pointerup\", () => {\n                // Collapse toolbar to compact mode when tapping outside of it\n                this.isToolbarExpanded = false;\n            });\n        } else {\n            // Mouse interaction behavior:\n            // Close toolbar on mousedown and prevent it from opening until mouseup.\n            this.addDomListener(this.editable, \"mousedown\", (ev) => {\n                this.closeToolbar(this.dependencies.selection.getSelectionData());\n                this.debouncedUpdateToolbar.cancel();\n                this.onSelectionChangeActive = false;\n            });\n            this.addGlobalDomListener(\"mouseup\", (ev) => {\n                if (ev.detail >= 2) {\n                    // Delayed open, waiting for a possible triple click.\n                    this.onSelectionChangeActive = true;\n                    this.debouncedUpdateToolbar();\n                } else {\n                    // Fast open, just wait for a possible selection change due\n                    // to mouseup.\n                    setTimeout(() => {\n                        this.updateToolbar();\n                        this.onSelectionChangeActive = true;\n                    });\n                }\n            });\n\n            // Keyboard interaction behavior:\n            // Close toolbar on keydown Arrows and prevent it from opening until\n            // keyup. Opening is debounced to avoid open/close between\n            // sequential keystrokes.\n            this.addDomListener(this.editable, \"keydown\", (ev) => {\n                // reason for \"key?\":\n                // On Chrome, if there is a password saved for a login page,\n                // a mouse click trigger a keydown event without any key\n                if (ev.key?.startsWith(\"Arrow\")) {\n                    this.closeToolbar(this.dependencies.selection.getSelectionData());\n                    this.onSelectionChangeActive = false;\n                }\n            });\n            this.addDomListener(this.editable, \"keyup\", (ev) => {\n                if (ev.key?.startsWith(\"Arrow\")) {\n                    this.onSelectionChangeActive = true;\n                    this.debouncedUpdateToolbar();\n                }\n            });\n        }\n        this.isToolbarExpanded = false;\n        this.toolbarProps = {\n            class: \"shadow rounded my-2\",\n            getSelection: () => this.dependencies.selection.getSelectionData(),\n            focusEditable: () => this.dependencies.selection.focusEditable(),\n            state: this.state,\n        };\n    }\n\n    destroy() {\n        this.debouncedUpdateToolbar.cancel();\n        this.updateToolbar.cancel();\n        this.overlay.close();\n        super.destroy();\n    }\n\n    /**\n     * @returns {ToolbarButton[]}\n     */\n    getButtons() {\n        /** @type {ToolbarItem[]} */\n        const toolbarItems = this.getResource(\"toolbar_items\");\n\n        /** @type {(item: ToolbarCommandItem) => ToolbarCommandButton} */\n        const commandItemToButton = (item) => {\n            const command = this.dependencies.userCommand.getCommand(item.commandId);\n            return composeToolbarButton(command, item);\n        };\n        /** @type {(item: ToolbarComponentItem) => ToolbarComponentButton} */\n        const componentItemToButton = (item) => ({\n            isAvailable: () => true,\n            ...item,\n            description:\n                item.description instanceof Function ? item.description : () => item.description,\n        });\n\n        return toolbarItems.map((item) =>\n            \"Component\" in item ? componentItemToButton(item) : commandItemToButton(item)\n        );\n    }\n\n    getButtonGroups() {\n        const buttons = this.getButtons();\n        /** @type {ToolbarGroup[]} */\n        const groups = this.getResource(\"toolbar_groups\");\n\n        return groups.map((group) => ({\n            ...omit(group, \"namespaces\"),\n            buttons: buttons\n                .filter((button) => button.groupId === group.id)\n                .map((button) => ({\n                    ...button,\n                    namespaces: button.namespaces || group.namespaces || [\"expanded\"],\n                })),\n        }));\n    }\n\n    /**\n     * @returns ToolbarButton[]\n     */\n    getButtonsForNamespace(namespace) {\n        if (this.buttonsByNamespace[namespace]) {\n            return this.buttonsByNamespace[namespace];\n        }\n        const button = this.buttonGroups.flatMap((group) =>\n            group.buttons.filter((btn) => btn.namespaces.includes(namespace))\n        );\n        this.buttonsByNamespace[namespace] = button;\n        return button;\n    }\n\n    getToolbarInfo() {\n        return {\n            buttonGroups: this.buttonGroups,\n        };\n    }\n\n    handleSelectionChange(selectionData) {\n        if (this.onSelectionChangeActive) {\n            this.updateToolbar(selectionData);\n        }\n    }\n\n    /**\n     * Different handlers might call updateToolbar (e.g. step added and\n     * selection change) in the same tick. To avoid unnecessary updates, we\n     * batch the calls.\n     */\n    updateToolbar = debounce(this._updateToolbar, 0, { trailing: true });\n    _updateToolbar(selectionData = this.dependencies.selection.getSelectionData()) {\n        // Prevent toolbar to open if the selection is not in the editable area,\n        // or if the selection is protected or protecting.\n        if (\n            !selectionData.currentSelectionIsInEditable ||\n            selectionData.documentSelectionIsProtected ||\n            selectionData.documentSelectionIsProtecting\n        ) {\n            this.closeToolbar();\n            return;\n        }\n        // Prevent toolbar to open if the selection is only non-editable nodes.\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        if (targetedNodes.every((node) => !this.dependencies.selection.isNodeEditable(node))) {\n            this.closeToolbar();\n            return;\n        }\n        // Determine the namespace to use\n        let currentNamespace = null;\n        let filteredtargetedNodes = [];\n        filteredtargetedNodes = this.getFilteredTargetedNodes(targetedNodes);\n        for (const fn of this.getResource(\"toolbar_namespace_providers\")) {\n            currentNamespace = fn(filteredtargetedNodes, selectionData.editableSelection);\n            if (currentNamespace) {\n                break;\n            }\n        }\n\n        if (currentNamespace === DISABLED_NAMESPACE) {\n            this.closeToolbar();\n            return;\n        }\n\n        if (currentNamespace === \"compact\" && this.isToolbarExpanded) {\n            currentNamespace = \"expanded\";\n        }\n\n        if (currentNamespace) {\n            this.state.namespace = currentNamespace;\n            // Do not reposition the toolbar if it's already open.\n            if (!this.overlay.isOpen) {\n                this.overlay.open({ props: this.toolbarProps });\n            }\n            this.updateButtonsStates(selectionData.editableSelection, filteredtargetedNodes);\n        } else {\n            this.closeToolbar();\n        }\n    }\n\n    getFilteredTargetedNodes(targetedNodes) {\n        return targetedNodes\n            .filter(\n                (node) =>\n                    this.dependencies.selection.isNodeEditable(node) &&\n                    (node.nodeType !== Node.TEXT_NODE || (!isEmptyTextNode(node) && !isZWS(node)))\n            )\n            .filter((node) => {\n                const element = closestElement(node);\n                const style = this.document.defaultView.getComputedStyle(element);\n                return style.display !== \"none\" && style.visibility !== \"hidden\";\n            });\n    }\n\n    isToolbarVisible(targetedNodes, editableSelection) {\n        if (this.isMobileToolbar) {\n            return true;\n        }\n\n        const isCollapsed = editableSelection.isCollapsed;\n        if (isCollapsed) {\n            return false;\n        }\n        // Only allow the toolbar to open if the selection contains visible selected characters.\n        const selectionText = editableSelection.textContent();\n        const textCleaned = selectionText.replace(/(\\r\\n|\\n|\\r|\\u200B|\\uFEFF)/gm, \"\");\n        if (textCleaned.length) {\n            return true;\n        }\n        // Even without textContent we display the toolbar if the selection contains a <br>\n        return targetedNodes.some(\n            (node) => node.nodeType === Node.ELEMENT_NODE && node.tagName === \"BR\"\n        );\n    }\n\n    /**\n     * @param {SelectionData} selectionData\n     */\n    closeToolbar(selectionData = null) {\n        if (!this.overlay.isOpen) {\n            return;\n        }\n        // TODO: refactor candidate : Remove data-prevent-closing-overlay\n        const anchor = selectionData?.documentSelectionIsInEditable\n            ? selectionData.editableSelection?.anchorNode\n            : document.getSelection()?.anchorNode;\n        const shouldPreventClosing =\n            anchor?.closest?.(\"[data-prevent-closing-overlay]\")?.dataset?.preventClosingOverlay ===\n            \"true\";\n        if (!shouldPreventClosing) {\n            this.overlay.close();\n            this.isToolbarExpanded = false;\n            this.state.namespace = null;\n        }\n    }\n\n    /**\n     * @param {EditorSelection} selection\n     * @param {Node[]} targetedNodes\n     */\n    updateButtonsStates(selection, targetedNodes) {\n        const availableButtons = this.getAvailableButtonsSet(selection);\n        this.state.buttonGroups = this.buttonGroups\n            .map((group) => ({\n                id: group.id,\n                buttons: group.buttons\n                    .filter((button) => availableButtons.has(button))\n                    .map((button) => ({\n                        id: button.id,\n                        description: button.description(selection, targetedNodes),\n                        isDisabled: !!button.isDisabled?.(selection, targetedNodes),\n                        ...(button.Component\n                            ? pick(button, \"Component\", \"props\")\n                            : {\n                                  ...pick(button, \"run\", \"icon\", \"text\"),\n                                  isActive: !!button.isActive?.(selection, targetedNodes),\n                              }),\n                    })),\n            }))\n            // Filter out groups left empty\n            .filter((group) => group.buttons.length > 0);\n    }\n\n    /**\n     * Get the set of available buttons for the current namespace and selection.\n     *\n     * @param {EditorSelection} selection\n     * @returns {Set<ToolbarButton>}\n     */\n    getAvailableButtonsSet(selection) {\n        if (this.state.namespace === \"compact\") {\n            return this.getAvailableButtonsCompact(selection);\n        }\n        const isAvailable = (button) => button.isAvailable(selection);\n        return new Set(this.getButtonsForNamespace(this.state.namespace).filter(isAvailable));\n    }\n\n    /**\n     * We only display the toolbar in its compact form if the expanded form is\n     * larger than a threshold, and larger than the compact version itself.\n     * Otherwise, we display the expanded toolbar directly.\n     *\n     * @param {EditorSelection} selection\n     * @returns {Set<ToolbarButton>}\n     */\n    getAvailableButtonsCompact(selection) {\n        const isAvailable = memoize((button) => button.isAvailable(selection));\n        const compact = this.getButtonsForNamespace(\"compact\").filter(isAvailable);\n        const expanded = this.getButtonsForNamespace(\"expanded\").filter(isAvailable);\n        const shouldDisplayCompactToolbar =\n            // Expanded version is big enough\n            expanded.length >= MIN_SIZE_FOR_COMPACT &&\n            // Expanded version is bigger than the compact version\n            expanded.length > compact.length;\n        if (shouldDisplayCompactToolbar) {\n            return new Set(compact);\n        }\n        this.state.namespace = \"expanded\";\n        return new Set(expanded);\n    }\n}\n\nclass MobileToolbarOverlay {\n    constructor(editable) {\n        this.isOpen = false;\n        this.overlayId = `mobile_toolbar_${Math.random().toString(16).slice(2)}`;\n        this.editable = editable;\n    }\n\n    open({ props }) {\n        props.class = \"shadow\";\n        if (!this.isOpen) {\n            const modal = this.editable.closest(\".o_modal_full\");\n            if (modal) {\n                // Same height of the toolbar\n                modal.style.paddingBottom = \"40px\";\n            }\n            registry.category(\"main_components\").add(this.overlayId, {\n                Component: ToolbarMobile,\n                props,\n            });\n            this.isOpen = true;\n        }\n    }\n\n    close() {\n        const modal = this.editable.closest(\".o_modal_full\");\n        if (modal) {\n            modal.style.paddingBottom = \"\";\n        }\n        registry.category(\"main_components\").remove(this.overlayId, \"MobileToolbar\");\n        this.isOpen = false;\n    }\n}\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { Plugin } from \"../plugin\";\nimport { VideoSelector } from \"./media/media_dialog/video_selector\";\n\nexport const YOUTUBE_URL_GET_VIDEO_ID =\n    /^(?:(?:https?:)?\\/\\/)?(?:(?:www|m)\\.)?(?:youtube\\.com|youtu\\.be)(?:\\/(?:[\\w-]+\\?v=|embed\\/|v\\/)?)([^\\s?&#]+)(?:\\S+)?$/i;\n\nexport class YoutubePlugin extends Plugin {\n    static id = \"youtube\";\n    static dependencies = [\"history\", \"dom\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        ...(this.config.allowVideo && {\n            paste_media_url_command_providers: this.getCommandForVideoUrlPaste.bind(this),\n        }),\n    };\n    /**\n     * @param {string} url\n     */\n    getCommandForVideoUrlPaste(url) {\n        const youtubeUrl = YOUTUBE_URL_GET_VIDEO_ID.exec(url);\n        if (youtubeUrl) {\n            // URL is a YouTube video.\n            return {\n                title: _t(\"Embed Youtube Video\"),\n                description: _t(\"Embed the youtube video in the document.\"),\n                icon: \"fa-youtube-play\",\n                run: async () => {\n                    const videoElement = await this.getYoutubeVideoElement(youtubeUrl[0]);\n                    this.dependencies.dom.insert(videoElement);\n                    this.dependencies.history.addStep();\n                },\n            };\n        }\n    }\n    // @todo @phoenix: Should this be in this plugin?\n    /**\n     * @param {string} url\n     * @returns {HTMLElement} saved video element or undefined if the URL\n     * is not a valid YouTube video URL.\n     */\n    async getYoutubeVideoElement(url) {\n        if (!URL.canParse(url)) {\n            return;\n        }\n        const parsedUrl = new URL(url);\n        const urlParams = parsedUrl.searchParams;\n        const start_from = urlParams.get(\"start\") || urlParams.get(\"t\");\n\n        const autoplay = urlParams.get(\"autoplay\") === \"1\";\n        const loop = urlParams.get(\"loop\") === \"1\";\n        const hide_controls = urlParams.get(\"controls\") === \"0\";\n        const hide_fullscreen = urlParams.get(\"fs\") === \"0\";\n\n        const videoData = await rpc(\"/html_editor/video_url/data\", {\n            video_url: url,\n            autoplay,\n            loop,\n            hide_controls,\n            hide_fullscreen,\n            start_from,\n        });\n        const savedVideo = this.createVideoElement(videoData);\n        savedVideo.classList.add(...VideoSelector.mediaSpecificClasses);\n        return savedVideo;\n    }\n\n    createVideoElement(videoData) {\n        return VideoSelector.createElements([{ src: videoData.embed_url }])[0];\n    }\n}\n", "const urlParams = new URLSearchParams(window.location.search);\nconst collaborationDebug = urlParams.get(\"collaborationDebug\");\nconst COLLABORATION_LOCALSTORAGE_KEY = \"odoo_editor_collaboration_debug\";\nif (typeof collaborationDebug === \"string\") {\n    if (collaborationDebug === \"false\") {\n        localStorage.removeItem(\n            COLLABORATION_LOCALSTORAGE_KEY,\n            urlParams.get(\"collaborationDebug\")\n        );\n    } else {\n        localStorage.setItem(COLLABORATION_LOCALSTORAGE_KEY, urlParams.get(\"collaborationDebug\"));\n    }\n}\nconst debugValue = localStorage.getItem(COLLABORATION_LOCALSTORAGE_KEY);\n\nconst debugShowLog = [\"\", \"true\", \"all\"].includes(debugValue);\nconst debugShowNotifications = debugValue === \"all\";\n\nconst baseNotificationMethods = {\n    ptp_request: async function (notification) {\n        const { requestId, requestName, requestPayload, requestTransport } =\n            notification.notificationPayload;\n        this._onRequest(\n            notification.fromPeerId,\n            requestId,\n            requestName,\n            requestPayload,\n            requestTransport\n        );\n    },\n    ptp_request_result: function (notification) {\n        const { requestId, result } = notification.notificationPayload;\n        // If not in _pendingRequestResolver, it means it has timeout.\n        if (this._pendingRequestResolver[requestId]) {\n            clearTimeout(this._pendingRequestResolver[requestId].rejectTimeout);\n            this._pendingRequestResolver[requestId].resolve(result);\n            delete this._pendingRequestResolver[requestId];\n        }\n    },\n\n    ptp_join: async function (notification) {\n        const peerId = notification.fromPeerId;\n        if (this.peersInfos[peerId] && this.peersInfos[peerId].peerConnection) {\n            return this.peersInfos[peerId];\n        }\n        this._createPeer(peerId);\n    },\n\n    rtc_signal_icecandidate: async function (notification) {\n        if (debugShowLog) {\n            console.log(`%creceive candidate`, \"background: darkgreen; color: white;\");\n        }\n        const peerInfos = this.peersInfos[notification.fromPeerId];\n        if (\n            !peerInfos ||\n            !peerInfos.peerConnection ||\n            peerInfos.peerConnection.connectionState === \"closed\"\n        ) {\n            console.groupCollapsed(\"=== ERROR: Handle Ice Candidate from undefined|closed ===\");\n            console.trace(peerInfos);\n            console.groupEnd();\n            return;\n        }\n        if (!peerInfos.peerConnection.remoteDescription) {\n            peerInfos.iceCandidateBuffer.push(notification.notificationPayload);\n        } else {\n            this._addIceCandidate(peerInfos, notification.notificationPayload);\n        }\n    },\n    rtc_signal_description: async function (notification) {\n        const description = notification.notificationPayload;\n        if (debugShowLog) {\n            console.log(\n                `%cdescription received:`,\n                \"background: blueviolet; color: white;\",\n                description\n            );\n        }\n\n        const peerInfos =\n            this.peersInfos[notification.fromPeerId] || this._createPeer(notification.fromPeerId);\n        const pc = peerInfos.peerConnection;\n\n        if (!pc || pc.connectionState === \"closed\") {\n            if (debugShowLog) {\n                console.groupCollapsed(\"=== ERROR: handle offer ===\");\n                console.log(\n                    \"An offer has been received for a non-existent peer connection - peer: \" +\n                        notification.fromPeerId\n                );\n                console.trace(pc && pc.connectionState);\n                console.groupEnd();\n            }\n            return;\n        }\n\n        // Skip if we already have an offer.\n        if (pc.signalingState === \"have-remote-offer\") {\n            return;\n        }\n\n        // If there is a racing conditing with the signaling offer (two\n        // being sent at the same time). We need one peer that abort by\n        // rollbacking to a stable signaling state where the other is\n        // continuing the process. The peer that is polite is the one that\n        // will rollback.\n        const isPolite =\n            (\"\" + notification.fromPeerId).localeCompare(\"\" + this._currentPeerId) === 1;\n        if (debugShowLog) {\n            console.log(\n                `%cisPolite: %c${isPolite}`,\n                \"background: deepskyblue;\",\n                `background:${isPolite ? \"green\" : \"red\"}`\n            );\n        }\n\n        const isOfferRacing =\n            description.type === \"offer\" &&\n            (peerInfos.makingOffer || pc.signalingState !== \"stable\");\n        // If there is a racing conditing with the signaling offer and the\n        // peer is impolite, we must not process this offer and wait for\n        // the answer for the signaling process to continue.\n        if (isOfferRacing && !isPolite) {\n            if (debugShowLog) {\n                console.log(\n                    `%creturn because isOfferRacing && !isPolite. pc.signalingState: ${pc.signalingState}`,\n                    \"background: red;\"\n                );\n            }\n            return;\n        }\n        if (debugShowLog) {\n            console.log(`%cisOfferRacing: ${isOfferRacing}`, \"background: red;\");\n            console.log(`%c SETREMOTEDESCRIPTION`, \"background: navy; color:white;\");\n        }\n        try {\n            await pc.setRemoteDescription(description);\n        } catch (e) {\n            if (e instanceof DOMException && e.name === \"InvalidStateError\") {\n                console.error(e);\n                return;\n            } else {\n                throw e;\n            }\n        }\n        if (peerInfos.iceCandidateBuffer.length) {\n            for (const candidate of peerInfos.iceCandidateBuffer) {\n                await this._addIceCandidate(peerInfos, candidate);\n            }\n            peerInfos.iceCandidateBuffer.splice(0);\n        }\n        if (description.type === \"offer\") {\n            const answerDescription = await pc.createAnswer();\n            try {\n                await pc.setLocalDescription(answerDescription);\n            } catch (e) {\n                if (e instanceof DOMException && e.name === \"InvalidStateError\") {\n                    console.error(e);\n                    return;\n                } else {\n                    throw e;\n                }\n            }\n            this.notifyPeer(notification.fromPeerId, \"rtc_signal_description\", pc.localDescription);\n        }\n    },\n};\n\nexport class PeerToPeer {\n    constructor(options) {\n        this.options = options;\n        this._currentPeerId = this.options.currentPeerId;\n        if (debugShowLog) {\n            console.log(\n                `%c currentPeerId:${this._currentPeerId}`,\n                \"background: blue; color: white;\"\n            );\n        }\n\n        // peerId -> PeerInfos\n        this.peersInfos = {};\n        this._lastRequestId = -1;\n        this._pendingRequestResolver = {};\n        this._stopped = false;\n    }\n\n    stop() {\n        this.closeAllConnections();\n        this._stopped = true;\n    }\n\n    getConnectedPeerIds() {\n        return Object.entries(this.peersInfos)\n            .filter(\n                ([id, infos]) =>\n                    infos.peerConnection &&\n                    infos.peerConnection.iceConnectionState === \"connected\" &&\n                    infos.dataChannel &&\n                    infos.dataChannel.readyState === \"open\"\n            )\n            .map(([id]) => id);\n    }\n\n    removePeer(peerId) {\n        if (debugShowLog) {\n            console.log(`%c REMOVE PEER ${peerId}`, \"background: chocolate;\");\n        }\n        this.notifySelf(\"ptp_remove\", peerId);\n        const peerInfos = this.peersInfos[peerId];\n        if (!peerInfos) {\n            return;\n        }\n        clearTimeout(peerInfos.fallbackTimeout);\n        clearTimeout(peerInfos.zombieTimeout);\n        peerInfos.dataChannel && peerInfos.dataChannel.close();\n        peerInfos.peerConnection && peerInfos.peerConnection.close();\n        delete this.peersInfos[peerId];\n    }\n\n    closeAllConnections() {\n        for (const peerId of Object.keys(this.peersInfos)) {\n            this.notifyAllPeers(\"ptp_disconnect\");\n            this.removePeer(peerId);\n        }\n    }\n\n    async notifyAllPeers(notificationName, notificationPayload, { transport = \"server\" } = {}) {\n        if (this._stopped) {\n            return;\n        }\n        const transportPayload = {\n            fromPeerId: this._currentPeerId,\n            notificationName,\n            notificationPayload,\n        };\n        if (transport === \"server\") {\n            await this.options.broadcastAll(transportPayload);\n        } else if (transport === \"rtc\") {\n            for (const cliendId of Object.keys(this.peersInfos)) {\n                this._channelNotify(cliendId, transportPayload);\n            }\n        } else {\n            throw new Error(\n                `Transport \"${transport}\" is not supported. Use \"server\" or \"rtc\" transport.`\n            );\n        }\n    }\n\n    notifyPeer(peerId, notificationName, notificationPayload, { transport = \"server\" } = {}) {\n        if (this._stopped) {\n            return;\n        }\n        if (debugShowNotifications) {\n            if (notificationName === \"ptp_request_result\") {\n                console.log(\n                    `%c${Date.now()} - REQUEST RESULT SEND: %c${transport}:${\n                        notificationPayload.requestId\n                    }:${this._currentPeerId.slice(\"-5\")}:${peerId.slice(\"-5\")}`,\n                    \"color: #aaa;font-weight:bold;\",\n                    \"color: #aaa;font-weight:normal\"\n                );\n            } else if (notificationName === \"ptp_request\") {\n                console.log(\n                    `%c${Date.now()} - REQUEST SEND: %c${transport}:${\n                        notificationPayload.requestName\n                    }|${notificationPayload.requestId}:${this._currentPeerId.slice(\n                        \"-5\"\n                    )}:${peerId.slice(\"-5\")}`,\n                    \"color: #aaa;font-weight:bold;\",\n                    \"color: #aaa;font-weight:normal\"\n                );\n            } else {\n                console.log(\n                    `%c${Date.now()} - NOTIFICATION SEND: %c${transport}:${notificationName}:${this._currentPeerId.slice(\n                        \"-5\"\n                    )}:${peerId.slice(\"-5\")}`,\n                    \"color: #aaa;font-weight:bold;\",\n                    \"color: #aaa;font-weight:normal\"\n                );\n            }\n        }\n        const transportPayload = {\n            fromPeerId: this._currentPeerId,\n            toPeerId: peerId,\n            notificationName,\n            notificationPayload,\n        };\n        if (transport === \"server\") {\n            this.options.broadcastAll(transportPayload);\n        } else if (transport === \"rtc\") {\n            this._channelNotify(peerId, transportPayload);\n        } else {\n            throw new Error(\n                `Transport \"${transport}\" is not supported. Use \"server\" or \"rtc\" transport.`\n            );\n        }\n    }\n\n    notifySelf(notificationName, notificationPayload) {\n        if (this._stopped) {\n            return;\n        }\n        return this.handleNotification({ notificationName, notificationPayload });\n    }\n\n    handleNotification(notification) {\n        if (this._stopped) {\n            return;\n        }\n        const isInternalNotification =\n            typeof notification.fromPeerId === \"undefined\" &&\n            typeof notification.toPeerId === \"undefined\";\n        if (\n            isInternalNotification ||\n            (notification.fromPeerId !== this._currentPeerId && !notification.toPeerId) ||\n            notification.toPeerId === this._currentPeerId\n        ) {\n            if (debugShowNotifications) {\n                if (notification.notificationName === \"ptp_request_result\") {\n                    console.log(\n                        `%c${Date.now()} - REQUEST RESULT RECEIVE: %c${\n                            notification.notificationPayload.requestId\n                        }:${notification.fromPeerId.slice(\"-5\")}:${notification.toPeerId.slice(\n                            \"-5\"\n                        )}`,\n                        \"color: #aaa;font-weight:bold;\",\n                        \"color: #aaa;font-weight:normal\"\n                    );\n                } else if (notification.notificationName === \"ptp_request\") {\n                    console.log(\n                        `%c${Date.now()} - REQUEST RECEIVE: %c${\n                            notification.notificationPayload.requestName\n                        }|${\n                            notification.notificationPayload.requestId\n                        }:${notification.fromPeerId.slice(\"-5\")}:${notification.toPeerId.slice(\n                            \"-5\"\n                        )}`,\n                        \"color: #aaa;font-weight:bold;\",\n                        \"color: #aaa;font-weight:normal\"\n                    );\n                } else {\n                    console.log(\n                        `%c${Date.now()} - NOTIFICATION RECEIVE: %c${\n                            notification.notificationName\n                        }:${notification.fromPeerId}:${notification.toPeerId}`,\n                        \"color: #aaa;font-weight:bold;\",\n                        \"color: #aaa;font-weight:normal\"\n                    );\n                }\n            }\n            try {\n                const baseMethod = baseNotificationMethods[notification.notificationName];\n                if (baseMethod) {\n                    return baseMethod.call(this, notification);\n                }\n                if (this.options.onNotification) {\n                    return this.options.onNotification(notification);\n                }\n            } catch (error) {\n                console.groupCollapsed(\"=== ERROR: On notification in collaboration ===\");\n                console.error(error);\n                console.groupEnd();\n            }\n        }\n    }\n\n    requestPeer(peerId, requestName, requestPayload, { transport = \"server\" } = {}) {\n        if (this._stopped) {\n            return;\n        }\n        return new Promise((resolve, reject) => {\n            const requestId = this._getRequestId();\n\n            const abort = (reason) => {\n                clearTimeout(rejectTimeout);\n                delete this._pendingRequestResolver[requestId];\n                reject(new RequestError(reason || \"Request was aborted.\"));\n            };\n            const rejectTimeout = setTimeout(\n                () => abort(\"Request took too long (more than 10 seconds).\"),\n                10000\n            );\n\n            this._pendingRequestResolver[requestId] = {\n                resolve,\n                rejectTimeout,\n                abort,\n            };\n\n            this.notifyPeer(\n                peerId,\n                \"ptp_request\",\n                {\n                    requestId,\n                    requestName,\n                    requestPayload,\n                    requestTransport: transport,\n                },\n                { transport }\n            );\n        });\n    }\n    abortCurrentRequests() {\n        for (const { abort } of Object.values(this._pendingRequestResolver)) {\n            abort();\n        }\n    }\n    _createPeer(peerId, { makeOffer = true } = {}) {\n        if (this._stopped) {\n            return;\n        }\n        if (debugShowLog) {\n            console.log(\"CREATE CONNECTION with peer id:\", peerId);\n        }\n        this.peersInfos[peerId] = {\n            makingOffer: false,\n            iceCandidateBuffer: [],\n            backoffFactor: 0,\n        };\n\n        if (!navigator.onLine) {\n            return this.peersInfos[peerId];\n        }\n        const pc = new RTCPeerConnection(this.options.peerConnectionConfig);\n\n        if (makeOffer) {\n            pc.onnegotiationneeded = async () => {\n                if (debugShowLog) {\n                    console.log(\n                        `%c NEGONATION NEEDED: ${pc.connectionState}`,\n                        \"background: deeppink;\"\n                    );\n                }\n                try {\n                    this.peersInfos[peerId].makingOffer = true;\n                    if (debugShowLog) {\n                        console.log(\n                            `%ccreating and sending an offer`,\n                            \"background: darkmagenta; color: white;\"\n                        );\n                    }\n                    const offer = await pc.createOffer();\n                    // Avoid race condition.\n                    if (pc.signalingState !== \"stable\") {\n                        return;\n                    }\n                    await pc.setLocalDescription(offer);\n                    this.notifyPeer(peerId, \"rtc_signal_description\", pc.localDescription);\n                } catch (err) {\n                    console.error(err);\n                } finally {\n                    this.peersInfos[peerId].makingOffer = false;\n                }\n            };\n        }\n        pc.onicecandidate = async (event) => {\n            if (event.candidate) {\n                this.notifyPeer(peerId, \"rtc_signal_icecandidate\", event.candidate);\n            }\n        };\n        pc.oniceconnectionstatechange = async () => {\n            if (debugShowLog) {\n                console.log(\"ICE STATE UPDATE: \" + pc.iceConnectionState);\n            }\n\n            switch (pc.iceConnectionState) {\n                case \"failed\":\n                case \"closed\":\n                    this.removePeer(peerId);\n                    break;\n                case \"disconnected\":\n                    if (navigator.onLine) {\n                        await this._recoverConnection(peerId, {\n                            delay: 3000,\n                            reason: \"ice connection disconnected\",\n                        });\n                    }\n                    break;\n                case \"connected\":\n                    this.peersInfos[peerId].backoffFactor = 0;\n                    break;\n            }\n        };\n        // This event does not work in FF. Let's try with oniceconnectionstatechange if it is sufficient.\n        pc.onconnectionstatechange = async () => {\n            if (debugShowLog) {\n                console.log(\"CONNECTION STATE UPDATE:\" + pc.connectionState);\n            }\n\n            switch (pc.connectionState) {\n                case \"failed\":\n                case \"closed\":\n                    this.removePeer(peerId);\n                    break;\n                case \"disconnected\":\n                    if (navigator.onLine) {\n                        await this._recoverConnection(peerId, {\n                            delay: 3000,\n                            reason: \"connection disconnected\",\n                        });\n                    }\n                    break;\n                case \"connected\":\n                case \"completed\":\n                    this.peersInfos[peerId].backoffFactor = 0;\n                    break;\n            }\n        };\n        pc.onicecandidateerror = async (error) => {\n            if (debugShowLog) {\n                console.groupCollapsed(\"=== ERROR: onIceCandidate ===\");\n                console.log(\n                    \"connectionState: \" +\n                        pc.connectionState +\n                        \" - iceState: \" +\n                        pc.iceConnectionState\n                );\n                console.trace(error);\n                console.groupEnd();\n            }\n            this._recoverConnection(peerId, { delay: 3000, reason: \"ice candidate error\" });\n        };\n        const dataChannel = pc.createDataChannel(\"notifications\", { negotiated: true, id: 1 });\n        let message = [];\n        dataChannel.onmessage = (event) => {\n            if (event.data !== \"-\") {\n                message.push(event.data);\n            } else {\n                this.handleNotification(JSON.parse(message.join(\"\")));\n                message = [];\n            }\n        };\n        dataChannel.onopen = (event) => {\n            this.notifySelf(\"rtc_data_channel_open\", {\n                connectionPeerId: peerId,\n            });\n        };\n\n        this.peersInfos[peerId].peerConnection = pc;\n        this.peersInfos[peerId].dataChannel = dataChannel;\n\n        return this.peersInfos[peerId];\n    }\n    async _addIceCandidate(peerInfos, candidate) {\n        const rtcIceCandidate = new RTCIceCandidate(candidate);\n        try {\n            await peerInfos.peerConnection.addIceCandidate(rtcIceCandidate);\n        } catch (error) {\n            // Ignored.\n            console.groupCollapsed(\"=== ERROR: ADD ICE CANDIDATE ===\");\n            console.trace(error);\n            console.groupEnd();\n        }\n    }\n\n    _channelNotify(peerId, transportPayload) {\n        if (this._stopped) {\n            return;\n        }\n        const peerInfo = this.peersInfos[peerId];\n        const dataChannel = peerInfo && peerInfo.dataChannel;\n\n        if (!dataChannel || dataChannel.readyState !== \"open\") {\n            if (peerInfo && !peerInfo.zombieTimeout) {\n                if (debugShowLog) {\n                    console.warn(\n                        `Impossible to communicate with peer ${peerId}. The connection will be killed in 10 seconds if the datachannel state has not changed.`\n                    );\n                }\n                this._killPotentialZombie(peerId);\n            }\n        } else {\n            const str = JSON.stringify(transportPayload);\n            const size = str.length;\n            const maxStringLength = 5000;\n            let from = 0;\n            let to = maxStringLength;\n            while (from < size) {\n                dataChannel.send(str.slice(from, to));\n                from = to;\n                to = to += maxStringLength;\n            }\n            dataChannel.send(\"-\");\n        }\n    }\n\n    _getRequestId() {\n        this._lastRequestId++;\n        return this._lastRequestId;\n    }\n\n    async _onRequest(fromPeerId, requestId, requestName, requestPayload, requestTransport) {\n        if (this._stopped) {\n            return;\n        }\n        const requestFunction = this.options.onRequest && this.options.onRequest[requestName];\n        const result = await requestFunction({\n            fromPeerId,\n            requestId,\n            requestName,\n            requestPayload,\n        });\n        this.notifyPeer(\n            fromPeerId,\n            \"ptp_request_result\",\n            { requestId, result },\n            { transport: requestTransport }\n        );\n    }\n    /**\n     * Attempts a connection recovery by updating the tracks, which will start\n     * a new transaction: negotiationneeded -> offer -> answer -> ...\n     *\n     * @private\n     * @param {Object} [param1]\n     * @param {number} [param1.delay] in ms\n     * @param {string} [param1.reason]\n     */\n    _recoverConnection(peerId, { delay = 0, reason = \"\" } = {}) {\n        if (this._stopped) {\n            this.removePeer(peerId);\n            return;\n        }\n        const peerInfos = this.peersInfos[peerId];\n        if (!peerInfos || peerInfos.fallbackTimeout) {\n            return;\n        }\n        const backoffFactor = this.peersInfos[peerId].backoffFactor;\n        const backoffDelay = delay * Math.pow(2, backoffFactor);\n        // Stop trying to recover the connection after 10 attempts.\n        if (backoffFactor > 10) {\n            if (debugShowLog) {\n                console.log(\n                    `%c STOP RTC RECOVERY: impossible to connect to peer ${peerId}: ${reason}`,\n                    \"background: darkred; color: white;\"\n                );\n            }\n            return;\n        }\n\n        peerInfos.fallbackTimeout = setTimeout(async () => {\n            peerInfos.fallbackTimeout = undefined;\n            const pc = peerInfos.peerConnection;\n            if (!pc || pc.iceConnectionState === \"connected\") {\n                return;\n            }\n            if ([\"connected\", \"closed\"].includes(pc.connectionState)) {\n                return;\n            }\n            // hard reset: recreating a RTCPeerConnection\n            if (debugShowLog) {\n                console.log(\n                    `%c RTC RECOVERY: calling back peer ${peerId} to salvage the connection ${pc.iceConnectionState} after ${backoffDelay}ms, reason: ${reason}`,\n                    \"background: darkorange; color: white;\"\n                );\n            }\n            this.removePeer(peerId);\n            const newPeerInfos = this._createPeer(peerId);\n            newPeerInfos.backoffFactor = backoffFactor + 1;\n        }, backoffDelay);\n    }\n    // todo: do we try to salvage the connection after killing the zombie ?\n    // Maybe the salvage should be done when the connection is dropped.\n    _killPotentialZombie(peerId) {\n        if (this._stopped) {\n            this.removePeer(peerId);\n            return;\n        }\n        const peerInfos = this.peersInfos[peerId];\n        if (!peerInfos || peerInfos.zombieTimeout) {\n            return;\n        }\n\n        // If there is no connection after 10 seconds, terminate.\n        peerInfos.zombieTimeout = setTimeout(() => {\n            if (peerInfos && peerInfos.dataChannel && peerInfos.dataChannel.readyState !== \"open\") {\n                if (debugShowLog) {\n                    console.log(`%c KILL ZOMBIE ${peerId}`, \"background: red;\");\n                }\n                this.removePeer(peerId);\n            } else {\n                if (debugShowLog) {\n                    console.log(`%c NOT A ZOMBIE ${peerId}`, \"background: green;\");\n                }\n            }\n        }, 10000);\n    }\n}\n\nexport class RequestError extends Error {\n    constructor(message) {\n        super(message);\n        this.name = \"RequestError\";\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { user } from \"@web/core/user\";\nimport { Mutex } from \"@web/core/utils/concurrency\";\nimport { debounce } from \"@web/core/utils/timing\";\nimport { PeerToPeer, RequestError } from \"./PeerToPeer\";\nimport { ancestors } from \"@html_editor/utils/dom_traversal\";\nimport { childNodeIndex } from \"@html_editor/utils/position\";\n\n/**\n * @typedef {Object} CollaborationSelection\n * @property {import(\"@html_editor/core/history_plugin\").SerializedSelection} selection\n * @property {string} color\n * @property {string} peerId\n */\n\n// Time to consider a user offline in ms. This fixes the problem of the\n// navigator closing rtc connection when the mac laptop screen is closed.\n// const CONSIDER_OFFLINE_TIME = 1000;\n// Check wether the computer could be offline. This fixes the problem of the\n// navigator closing rtc connection when the mac laptop screen is closed.\n// This case happens on Mac OS on every browser when the user close it's laptop\n// screen. At first, the os/navigator closes all rtc connection, and after some\n// times, the os/navigator internet goes offline without triggering an\n// offline/online event.\n// However, if the laptop screen is open and the connection is properly remove\n// (e.g. disconnect wifi), the event is properly triggered.\n// const CHECK_OFFLINE_TIME = 1000;\n// const PTP_PEER_DISCONNECTED_STATES = [\"failed\", \"closed\", \"disconnected\"];\n\n// Time in ms to wait when trying to aggregate snapshots from other peers and\n// potentially recover from a missing step before trying to apply those\n// snapshots or recover from the server.\nconst PTP_MAX_RECOVERY_TIME = 500;\n\nconst REQUEST_ERROR = Symbol(\"REQUEST_ERROR\");\n\n// this is a local cache for ice server descriptions\nlet ICE_SERVERS = null;\n\n/**\n * @typedef { Object } CollaborationOdooShared\n * @property { CollaborationOdooPlugin['getPeerMetadata'] } getPeerMetadata\n */\n\nexport class CollaborationOdooPlugin extends Plugin {\n    static id = \"collaborationOdoo\";\n    static dependencies = [\"baseContainer\", \"history\", \"collaboration\", \"selection\"];\n    static shared = [\"getPeerMetadata\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        selectionchange_handlers: debounce(() => {\n            this.ptp?.notifyAllPeers(\n                \"oe_history_set_selection\",\n                this.getCurrentCollaborativeSelection(),\n                {\n                    transport: \"rtc\",\n                }\n            );\n        }, 50),\n        clean_for_save_handlers: ({ root }) => this.attachHistoryIds(root),\n        history_missing_parent_step_handlers: this.onHistoryMissingParentStep.bind(this),\n        history_reset_handlers: this.onReset.bind(this),\n        step_added_handlers: ({ step }) =>\n            this.ptp?.notifyAllPeers(\"oe_history_step\", step, { transport: \"rtc\" }),\n    };\n\n    setup() {\n        this.isDocumentStale = false;\n\n        this.ptpJoined = false;\n\n        // Each time a reset of the document is triggered, it is assigned a\n        // unique identifier. Since resetting the editor involves asynchronous\n        // requests, it is possible that subsequent resets are triggered before\n        // the previous one is complete. This property identifies the latest\n        // reset and can be compared against to cancel the processing of late\n        // responses from previous resets.\n        this.lastCollaborationResetId = 0;\n\n        // The ID is the latest step ID that the server knows through\n        // `data-last-history-steps`. We cannot save to the server if we do not\n        // have that ID in our history ids as it means that our version is\n        // stale.\n        this.serverLastStepId =\n            this.config.content && this.getLastHistoryStepId(this.config.content);\n\n        this.setupCollaboration(this.config.collaboration.collaborationChannel);\n\n        const collaborativeTrigger = this.config.collaboration.collaborativeTrigger;\n        this.joinPeerToPeer = this.joinPeerToPeer.bind(this);\n        if (collaborativeTrigger === \"start\") {\n            this.joinPeerToPeer();\n        } else if (\n            collaborativeTrigger === \"focus\" ||\n            typeof collaborativeTrigger === \"undefined\"\n        ) {\n            // Wait until editor is focused to join the peer to peer network.\n            this.editable.addEventListener(\"focus\", this.joinPeerToPeer);\n        }\n\n        stripHistoryIds(this.editable);\n    }\n    destroy() {\n        this.collaborationStopBus && this.collaborationStopBus();\n        // If peer to peer is initializing, wait for properly closing it.\n        if (this.peerToPeerLoading) {\n            this.peerToPeerLoading.then(() => {\n                this.stopPeerToPeer();\n            });\n        }\n        // todo: to implement\n        // clearInterval(this.collaborationInterval);\n        super.destroy();\n    }\n\n    stopPeerToPeer() {\n        this.joiningPtp = false;\n        this.ptpJoined = false;\n        this.resetCollabRequests();\n        this.ptp && this.ptp.stop();\n    }\n\n    getCurrentCollaborativeSelection() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        return {\n            selection: this.dependencies.history.serializeSelection(selection),\n            peerId: this.config.collaboration.peerId,\n        };\n    }\n    setupCollaboration(collaborationChannel) {\n        const modelName = collaborationChannel.collaborationModelName;\n        const fieldName = collaborationChannel.collaborationFieldName;\n        const resId = collaborationChannel.collaborationResId;\n        const channelName = `editor_collaboration:${modelName}:${fieldName}:${resId}`;\n\n        if (\n            !(modelName && fieldName && resId)\n            // todo: handle this feature\n            // || Wysiwyg.activeCollaborationChannelNames.has(channelName)\n        ) {\n            return;\n        }\n\n        this.collaborationChannelName = channelName;\n        this.historyStepsBuffer = [];\n        // Wysiwyg.activeCollaborationChannelNames.add(channelName);\n\n        const collaborationBusListener = (payload) => {\n            if (\n                payload.model_name === modelName &&\n                payload.field_name === fieldName &&\n                payload.res_id === resId\n            ) {\n                if (payload.notificationName === \"html_field_write\") {\n                    this.onServerLastIdUpdate(payload.notificationPayload.last_step_id);\n                } else if (this.ptpJoined) {\n                    this.peerToPeerLoading.then(() => this.ptp.handleNotification(payload));\n                }\n            }\n        };\n        const { busService } = this.config.collaboration;\n        busService.subscribe(\"editor_collaboration\", collaborationBusListener);\n        busService.addChannel(this.collaborationChannelName);\n        this.collaborationStopBus = () => {\n            // Wysiwyg.activeCollaborationChannelNames.delete(this.collaborationChannelName);\n            busService.unsubscribe(\"editor_collaboration\", collaborationBusListener);\n            busService.deleteChannel(this.collaborationChannelName);\n        };\n\n        this.startCollaborationTime = new Date().getTime();\n\n        // this.checkConnectionChange = () => {\n        //     if (!this.ptp) {\n        //         return;\n        //     }\n        //     if (!navigator.onLine) {\n        //         this.signalOffline();\n        //     } else {\n        //         this.signalOnline();\n        //     }\n        // };\n\n        // window.addEventListener(\"online\", this.checkConnectionChange);\n        // window.addEventListener(\"offline\", this.checkConnectionChange);\n\n        // this.collaborationInterval = setInterval(async () => {\n        //     if (this.offlineTimeout || this.preSavePromise || !this.ptp) {\n        //         return;\n        //     }\n\n        //     const peersInfos = Object.values(this.ptp.peersInfos);\n        //     const couldBeDisconnected =\n        //         Boolean(peersInfos.length) &&\n        //         peersInfos.every((x) =>\n        //             PTP_PEER_DISCONNECTED_STATES.includes(\n        //                 x.peerConnection && x.peerConnection.connectionState\n        //             )\n        //         );\n\n        //     if (couldBeDisconnected) {\n        //         this.offlineTimeout = setTimeout(() => {\n        //             this.signalOffline();\n        //         }, CONSIDER_OFFLINE_TIME);\n        //     }\n        // }, CHECK_OFFLINE_TIME);\n\n        const loadPeerToPeer = async () => {\n            if (!ICE_SERVERS) {\n                ICE_SERVERS = await rpc(\"/html_editor/get_ice_servers\");\n            }\n\n            let iceServers = ICE_SERVERS;\n            if (!iceServers.length) {\n                iceServers = [\n                    {\n                        urls: [\"stun:stun1.l.google.com:19302\", \"stun:stun2.l.google.com:19302\"],\n                    },\n                ];\n            }\n            this.iceServers = iceServers;\n\n            this.ptp = this.getNewPtp();\n        };\n\n        this.peerToPeerLoading = loadPeerToPeer();\n    }\n\n    getNewPtp() {\n        const rpcMutex = new Mutex();\n        const { collaborationChannel } = this.config.collaboration;\n        const modelName = collaborationChannel.collaborationModelName;\n        const fieldName = collaborationChannel.collaborationFieldName;\n        const resId = collaborationChannel.collaborationResId;\n\n        // Wether or not the history has been sent or received at least\n        // once.\n        this.historySyncAtLeastOnce = false;\n\n        return new PeerToPeer({\n            peerConnectionConfig: { iceServers: this.iceServers },\n            currentPeerId: this.config.collaboration.peerId,\n            broadcastAll: (rpcData) =>\n                rpcMutex.exec(async () =>\n                    rpc(\"/html_editor/bus_broadcast\", {\n                        model_name: modelName,\n                        field_name: fieldName,\n                        res_id: resId,\n                        bus_data: rpcData,\n                    })\n                ),\n            onRequest: {\n                get_peer_metadata: this.getMetadata.bind(this),\n                get_missing_steps: (params) =>\n                    this.dependencies.collaboration.historyGetMissingSteps(params.requestPayload),\n                get_history_from_snapshot: () => this.getHistorySnapshot(),\n                get_collaborative_selection: () => this.getCurrentCollaborativeSelection(),\n                recover_document: (params) => {\n                    const { serverDocumentId, fromStepId } = params.requestPayload;\n                    if (\n                        !this.dependencies.collaboration.getBranchIds().includes(serverDocumentId)\n                    ) {\n                        return;\n                    }\n                    return {\n                        missingSteps: this.dependencies.collaboration.historyGetMissingSteps({\n                            fromStepId,\n                        }),\n                        snapshot: this.getHistorySnapshot(),\n                    };\n                },\n            },\n            onNotification: async (notification) => {\n                this.dispatchTo(\"collaboration_notification_handlers\", notification);\n                let { fromPeerId, notificationName, notificationPayload } = notification;\n                switch (notificationName) {\n                    case \"ptp_remove\":\n                        // todo: to implement\n                        // this.odooEditor.multiselectionRemove(notificationPayload);\n                        break;\n                    case \"ptp_disconnect\":\n                        this.ptp.removePeer(fromPeerId);\n                        // todo: to implement\n                        // this.odooEditor.multiselectionRemove(fromPeerId);\n                        break;\n                    case \"rtc_data_channel_open\": {\n                        fromPeerId = notificationPayload.connectionPeerId;\n                        const metadata = await this.requestPeer(\n                            fromPeerId,\n                            \"get_peer_metadata\",\n                            undefined,\n                            { transport: \"rtc\" }\n                        );\n                        if (metadata === REQUEST_ERROR) {\n                            return;\n                        }\n\n                        this.ptp.peersInfos[fromPeerId].metadata = metadata;\n\n                        if (!this.historySyncAtLeastOnce) {\n                            const localPeer = {\n                                id: this.config.collaboration.peerId,\n                                startTime: this.startCollaborationTime,\n                            };\n                            const remotePeer = {\n                                id: fromPeerId,\n                                startTime: metadata.startTime,\n                            };\n                            if (isPeerFirst(localPeer, remotePeer)) {\n                                this.historySyncAtLeastOnce = true;\n                                this.historySyncFinished = true;\n                            } else {\n                                this.resetCollabRequests();\n                                const response = await this.resetFromPeer(\n                                    fromPeerId,\n                                    this.lastCollaborationResetId\n                                );\n                                if (response === REQUEST_ERROR) {\n                                    return;\n                                }\n                            }\n                        } else {\n                            // Make both send their last step to each other to\n                            // ensure they are in sync.\n                            this.ptp.notifyAllPeers(\n                                \"oe_history_step\",\n                                this.dependencies.history.getHistorySteps().at(-1),\n                                { transport: \"rtc\" }\n                            );\n                            this.resetCollaborativeSelection(fromPeerId);\n                        }\n                        break;\n                    }\n                    case \"oe_history_step\":\n                        if (this.historySyncFinished) {\n                            this.dependencies.collaboration.onExternalHistorySteps([\n                                notificationPayload,\n                            ]);\n                        } else {\n                            this.historyStepsBuffer.push(notificationPayload);\n                        }\n                        break;\n                    case \"oe_history_set_selection\": {\n                        const peer = this.ptp.peersInfos[fromPeerId];\n                        if (!peer) {\n                            return;\n                        }\n                        const selection = notificationPayload;\n                        this.onExternalMultiselectionUpdate(selection);\n                        break;\n                    }\n                }\n            },\n        });\n    }\n    /**\n     * @param {string} peerId\n     */\n    getPeerMetadata(peerId) {\n        return this.ptp.peersInfos[peerId]?.metadata;\n    }\n    /**\n     * @param {CollaborationSelection} selection\n     */\n    onExternalMultiselectionUpdate(selection) {\n        this.dispatchTo(\"collaborative_selection_update_handlers\", selection);\n    }\n\n    async requestPeer(peerId, requestName, requestPayload, params) {\n        return this.ptp.requestPeer(peerId, requestName, requestPayload, params).catch((e) => {\n            if (e instanceof RequestError) {\n                return REQUEST_ERROR;\n            } else {\n                throw e;\n            }\n        });\n    }\n    getMetadata() {\n        const metadatas = {\n            startTime: this.startCollaborationTime,\n            peerName: user.name,\n        };\n        for (const cb of this.getResource(\"collaboration_peer_metadata_providers\")) {\n            Object.assign(metadatas, cb());\n        }\n        return metadatas;\n    }\n    /**\n     * Update the server document last step id and recover from a stale document\n     * if this peer does not have that step in its history.\n     */\n    onServerLastIdUpdate(last_step_id) {\n        this.serverLastStepId = last_step_id;\n        // Check if the current document is stale.\n        this.isDocumentStale = this.isLastDocumentStale();\n        if (this.isDocumentStale && this.ptpJoined) {\n            return this.recoverFromStaleDocument();\n        } else if (this.isDocumentStale && this.joiningPtp) {\n            // In case there is a stale document while a previous recovery is\n            // ongoing.\n            this.resetCollabRequests();\n            this.joinPeerToPeer();\n        }\n    }\n\n    joinPeerToPeer() {\n        this.editable.removeEventListener(\"focus\", this.joinPeerToPeer);\n        if (this.peerToPeerLoading) {\n            return this.peerToPeerLoading.then(async () => {\n                this.joiningPtp = true;\n                if (this.isDocumentStale) {\n                    const success = await this.resetFromServerAndResyncWithPeers();\n                    if (!success) {\n                        return;\n                    }\n                }\n                this.ptp.notifyAllPeers(\"ptp_join\");\n                this.joiningPtp = false;\n                this.ptpJoined = true;\n            });\n        }\n    }\n    isLastDocumentStale() {\n        if (!this.serverLastStepId) {\n            return false;\n        }\n        return !this.dependencies.collaboration.getBranchIds().includes(this.serverLastStepId);\n    }\n\n    /**\n     * Try to recover from a stale document.\n     *\n     * The strategy is:\n     *\n     * 1.  Try to get a converging document from the other peers.\n     *\n     * 1.1 By recovery from missing steps: it is the best possible case of\n     *     retrieval.\n     *\n     * 1.2 By recovery from snapshot: it reset the whole editor (destroying\n     *     changes and selection made by the user).\n     *\n     * 2. Reset from the server:\n     *    If the recovery from the other peers fails, reset from the server.\n     *\n     *    As we know we have a stale document, we need to reset it at least from\n     *    the server. We shouldn't wait too long for peers to respond because\n     *    the longer we wait for an unresponding peer, the longer a user can\n     *    edit a stale document.\n     *\n     *    The peers timeout is set to PTP_MAX_RECOVERY_TIME.\n     */\n    async recoverFromStaleDocument() {\n        return new Promise((resolve) => {\n            // 1. Try to recover a converging document from other peers.\n            const resetCollabCount = this.lastCollaborationResetId;\n\n            const allPeers = this.getPtpPeers().map((peer) => peer.id);\n\n            if (allPeers.length === 0) {\n                if (this.isDocumentStale) {\n                    this.showConflictDialog();\n                    resolve();\n                    return this.resetFromServerAndResyncWithPeers();\n                }\n            }\n\n            let hasRetrievalBudgetTimeout = false;\n            const snapshots = [];\n            let nbPendingResponses = allPeers.length;\n\n            const success = () => {\n                resolve();\n                clearTimeout(timeout);\n            };\n\n            for (const peerId of allPeers) {\n                this.requestPeer(\n                    peerId,\n                    \"recover_document\",\n                    {\n                        serverDocumentId: this.serverLastStepId,\n                        fromStepId: this.dependencies.collaboration.getBranchIds().at(-1),\n                    },\n                    { transport: \"rtc\" }\n                ).then((response) => {\n                    nbPendingResponses--;\n                    if (\n                        response === REQUEST_ERROR ||\n                        resetCollabCount !== this.lastCollaborationResetId ||\n                        hasRetrievalBudgetTimeout ||\n                        !response ||\n                        !this.isDocumentStale\n                    ) {\n                        if (nbPendingResponses <= 0) {\n                            processSnapshots();\n                        }\n                        return;\n                    }\n                    this.processMissingSteps(response.missingSteps);\n                    this.isDocumentStale = this.isLastDocumentStale();\n                    snapshots.push(response.snapshot);\n                    if (nbPendingResponses < 1) {\n                        processSnapshots();\n                    }\n                });\n            }\n\n            // Only process the snapshots after having received a response from all\n            // the peers or after PTP_MAX_RECOVERY_TIME in order to try to recover\n            // from missing steps.\n            const processSnapshots = async () => {\n                this.isDocumentStale = this.isLastDocumentStale();\n                if (!this.isDocumentStale) {\n                    return success();\n                }\n                if (snapshots[0]) {\n                    this.showConflictDialog();\n                }\n                for (const snapshot of snapshots) {\n                    this.applySnapshot(snapshot);\n                    this.isDocumentStale = this.isLastDocumentStale();\n                    // Prevent reseting from another snapshot if the document\n                    // converge.\n                    if (!this.isDocumentStale) {\n                        return success();\n                    }\n                }\n\n                // 2. If the document is still stale, try to recover from the server.\n                if (this.isDocumentStale) {\n                    this.showConflictDialog();\n                    await this.resetFromServerAndResyncWithPeers();\n                }\n\n                success();\n            };\n\n            // Wait PTP_MAX_RECOVERY_TIME to retrieve data from other peers to\n            // avoid reseting from the server if possible.\n            const timeout = setTimeout(() => {\n                if (resetCollabCount !== this.lastCollaborationResetId) {\n                    return;\n                }\n                hasRetrievalBudgetTimeout = true;\n                this.onRecoveryPeerTimeout(processSnapshots);\n            }, PTP_MAX_RECOVERY_TIME);\n        });\n    }\n\n    /**\n     * Get peer to peer peers.\n     */\n    getPtpPeers() {\n        const peers = Object.entries(this.ptp.peersInfos).map(([peerId, peerInfo]) => ({\n            id: peerId,\n            ...peerInfo,\n        }));\n        return peers.sort((a, b) => (isPeerFirst(a, b) ? -1 : 1));\n    }\n\n    getLastHistoryStepId(value) {\n        const matchId = value.match(/data-last-history-steps=\"[0-9,]*?([0-9]+)\"/);\n        return matchId && matchId[1];\n    }\n\n    resetCollabRequests() {\n        this.lastCollaborationResetId++;\n        // By aborting the current requests from ptp, we ensure that the ongoing\n        // `Wysiwyg.requestPeer` will return REQUEST_ERROR. Most requests that\n        // calls `Wysiwyg.requestPeer` might want to check if the response is\n        // REQUEST_ERROR.\n        this.ptp && this.ptp.abortCurrentRequests();\n    }\n    /**\n     * Reset the document from the server and resync with the peers.\n     */\n    async resetFromServerAndResyncWithPeers() {\n        let collaborationResetId = this.lastCollaborationResetId;\n        const record = await this.getCurrentRecord();\n        if (collaborationResetId !== this.lastCollaborationResetId) {\n            return;\n        }\n\n        const content =\n            record[this.config.collaboration.collaborationChannel.collaborationFieldName];\n        const lastHistoryId = content && this.getLastHistoryStepId(content);\n        // If a change was made in the document while retrieving it, the\n        // lastHistoryId will be different if the odoo bus did not have time to\n        // notify the user.\n        if (this.serverLastStepId !== lastHistoryId) {\n            // todo: instrument it to ensure it never happens\n            throw new Error(\n                \"Concurency detected while recovering from a stale document. The last history id of the server is different from the history id received by the html_field_write event.\"\n            );\n        }\n\n        this.isDocumentStale = false;\n        if (content) {\n            // content here is trusted\n            this.editable.innerHTML = content;\n        } else {\n            this.editable.replaceChildren(this.dependencies.baseContainer.createBaseContainer());\n        }\n        stripHistoryIds(this.editable);\n        this.dispatchTo(\"normalize_handlers\", this.editable);\n\n        this.dependencies.history.reset(content);\n\n        // After resetting from the server, try to resynchronise with a peer as\n        // if it was the first time connecting to a peer in order to retrieve a\n        // proper snapshot (e.g. This case could arise if we tried to recover\n        // from a peer but the timeout (PTP_MAX_RECOVERY_TIME) was reached\n        // before receiving a response).\n        this.historySyncAtLeastOnce = false;\n        this.resetCollabRequests();\n        collaborationResetId = this.lastCollaborationResetId;\n        this.startCollaborationTime = new Date().getTime();\n        await Promise.all(\n            this.getPtpPeers().map((peer) =>\n                // Reset from the fastest peer. The first peer to reset will set\n                // this.historySyncAtLeastOnce to true canceling the other peers\n                // resets.\n                this.resetFromPeer(peer.id, collaborationResetId)\n            )\n        );\n        return true;\n    }\n    onReset(content) {\n        // This ID correspond to the peer that initiated the document and set\n        // the initial oid for all nodes in the tree. It is not the same as\n        // document that had a step id at some point. If a step comes from a\n        // different history, we should not apply it.\n        this.historyShareId = Math.floor(Math.random() * Math.pow(2, 52)).toString();\n\n        const lastStepId = content && content.match(/data-last-history-steps=\"([\\d,]+)\"/)?.[1];\n        if (lastStepId) {\n            this.dependencies.collaboration.setInitialBranchStepId(lastStepId);\n        }\n    }\n\n    /**\n     * Process missing steps received from a peer.\n     *\n     * @private\n     * @param {Array<Object>|-1} missingSteps\n     * @return {Promise<boolean>} true if missing steps have been processed\n     */\n    async processMissingSteps(missingSteps) {\n        // If missing steps === -1, it means that either:\n        // - the step.peerId has a stale document\n        // - the step.peerId has a snapshot and does not includes the step in\n        //   its history\n        // - if another share history id\n        //   - because the step.peerId has reset from the server and\n        //     step.peerId is not synced with this peer\n        //   - because the step.peerId is in a network partition\n        if (missingSteps === -1 || !missingSteps.length) {\n            return false;\n        }\n        this.dependencies.collaboration.onExternalHistorySteps(missingSteps);\n        return true;\n    }\n    applySnapshot(snapshot) {\n        const { steps, historyIds, historyShareId } = snapshot;\n        // If there is no serverLastStepId, it means that we use a document\n        // that is not versionned yet.\n        const isStaleDocument =\n            this.serverLastStepId && !historyIds.includes(this.serverLastStepId);\n        if (isStaleDocument) {\n            return;\n        }\n        this.historyShareId = historyShareId;\n        this.historySyncAtLeastOnce = true;\n        this.dependencies.collaboration.resetFromSteps(steps, historyIds);\n\n        // todo: ensure that if the selection was not in the editable before the\n        // reset, it remains where it was after applying the snapshot.\n        return true;\n    }\n\n    /**\n     * Callback for when the timeout PTP_MAX_RECOVERY_TIME fires.\n     *\n     * Used to be hooked in tests.\n     *\n     * @param {Function} processSnapshots The snapshot processing function.\n     */\n    async onRecoveryPeerTimeout(processSnapshots) {\n        processSnapshots();\n    }\n    showConflictDialog() {\n        // todo: implement conflict dialog\n        // if (this.conflictDialogOpened) {\n        //     return;\n        // }\n        // const content = markup(this.odooEditor.editable.cloneNode(true).outerHTML);\n        // this.conflictDialogOpened = true;\n        // this.env.services.dialog.add(ConflictDialog, {\n        //     content,\n        //     close: () => (this.conflictDialogOpened = false),\n        // });\n    }\n\n    getHistorySnapshot() {\n        return Object.assign({}, this.dependencies.collaboration.getSnapshotSteps(), {\n            historyShareId: this.historyShareId,\n        });\n    }\n\n    async resetFromPeer(fromPeerId, resetCollabCount) {\n        this.historySyncFinished = false;\n        this.historyStepsBuffer = [];\n        const snapshot = await this.requestPeer(\n            fromPeerId,\n            \"get_history_from_snapshot\",\n            undefined,\n            { transport: \"rtc\" }\n        );\n        if (snapshot === REQUEST_ERROR) {\n            return REQUEST_ERROR;\n        }\n        if (resetCollabCount !== this.lastCollaborationResetId) {\n            return;\n        }\n        // Ensure that the history hasn't been synced by another peer before\n        // this `get_history_from_snapshot` finished.\n        if (this.historySyncAtLeastOnce) {\n            return;\n        }\n        const selection = this.dependencies.selection.getEditableSelection();\n        let anchorNodeIndexPath = this._getNodeIndexPath(selection.anchorNode);\n        let anchorOffset = selection.anchorOffset;\n        if (selection.anchorNode === this.editable) {\n            anchorNodeIndexPath = this._getNodeIndexPath(this.editable.firstChild);\n            anchorOffset = 0;\n        }\n        const applied = this.applySnapshot(snapshot);\n        if (!applied) {\n            return;\n        }\n        const anchorNode = this._getNodeFromIndexPath(anchorNodeIndexPath);\n        if (\n            this.dependencies.selection.isSelectionInEditable({ anchorNode, focusNode: anchorNode })\n        ) {\n            this.dependencies.selection.setSelection({\n                anchorNode,\n                anchorOffset,\n            });\n        }\n        this.historySyncFinished = true;\n        // In case there are steps received in the meantime, process them.\n        if (this.historyStepsBuffer.length) {\n            this.dependencies.collaboration.onExternalHistorySteps(this.historyStepsBuffer);\n            this.historyStepsBuffer = [];\n        }\n        this.editable.dispatchEvent(new CustomEvent(\"onHistoryResetFromPeer\"));\n        this.resetCollaborativeSelection(fromPeerId);\n    }\n\n    async resetCollaborativeSelection(fromPeerId) {\n        const remoteSelection = await this.requestPeer(\n            fromPeerId,\n            \"get_collaborative_selection\",\n            undefined,\n            { transport: \"rtc\" }\n        );\n        if (remoteSelection === REQUEST_ERROR) {\n            return;\n        }\n        if (remoteSelection) {\n            this.onExternalMultiselectionUpdate(remoteSelection);\n        }\n    }\n    async onHistoryMissingParentStep({ step, fromStepId }) {\n        if (!this.ptp) {\n            return;\n        }\n        const missingSteps = await this.requestPeer(\n            step.peerId,\n            \"get_missing_steps\",\n            {\n                fromStepId: fromStepId,\n                toStepId: step.id,\n            },\n            { transport: \"rtc\" }\n        );\n        if (missingSteps === REQUEST_ERROR) {\n            return;\n        }\n        this.processMissingSteps(\n            Array.isArray(missingSteps) ? missingSteps.concat(step) : missingSteps\n        );\n    }\n    async getCurrentRecord() {\n        const [record] = await this.config.collaboration.ormService.read(\n            this.config.collaboration.collaborationChannel.collaborationModelName,\n            [this.config.collaboration.collaborationChannel.collaborationResId],\n            [this.config.collaboration.collaborationChannel.collaborationFieldName]\n        );\n        return record;\n    }\n    attachHistoryIds(editable) {\n        const historyIds = this.dependencies.collaboration.getBranchIds().join(\",\");\n        const firstChild = editable.children[0];\n        if (firstChild) {\n            firstChild.setAttribute(\"data-last-history-steps\", historyIds);\n        }\n    }\n\n    /**\n     * Generates the path to a node as an array of indices, relative to a given ancestor.\n     *\n     * @param {Node} node - The node to trace the path for.\n     * @returns {number[]} The path as an array of child indices.\n     */\n    _getNodeIndexPath(node) {\n        return [node, ...ancestors(node, this.editable)].map((ancestor) =>\n            childNodeIndex(ancestor)\n        );\n    }\n    /**\n     * Finds a node in the DOM based on a path of child indices.\n     *\n     * @param {number[]} indexPath - The path as an array of child indices.\n     * @returns {Node|undefined} The node at the specified path, or null if not found.\n     */\n    _getNodeFromIndexPath(indexPath) {\n        return indexPath.reduceRight(\n            (node, index) => node?.childNodes?.[index],\n            this.editable.parentElement\n        );\n    }\n}\n\n/**\n * Check wether peerA is before peerB.\n */\nfunction isPeerFirst(peerA, peerB) {\n    if (peerA.startTime === peerB.startTime) {\n        return peerA.id.localeCompare(peerB.id) === -1;\n    }\n    if (peerA.startTime === undefined || peerB.startTime === undefined) {\n        return Boolean(peerA.startTime);\n    } else {\n        return peerA.startTime < peerB.startTime;\n    }\n}\n\nexport function stripHistoryIds(element) {\n    element\n        .querySelectorAll(\"[data-last-history-steps]\")\n        .forEach((el) => el.removeAttribute(\"data-last-history-steps\"));\n}\n", "import { Plugin } from \"@html_editor/plugin\";\n\n// 60 seconds\nexport const HISTORY_SNAPSHOT_INTERVAL = 1000 * 60;\n// 10 seconds\nconst HISTORY_SNAPSHOT_BUFFER_TIME = 1000 * 10;\n\n/**\n * @typedef { Object } CollaborationPluginConfig\n * @property { string } peerId\n *\n * @typedef { import(\"../../core/history_plugin\").HistoryStep } HistoryStep\n */\n\n/**\n * @typedef { Object } CollaborationShared\n * @property { CollaborationPlugin['getBranchIds'] } getBranchIds\n * @property { CollaborationPlugin['getSnapshotSteps'] } getSnapshotSteps\n * @property { CollaborationPlugin['historyGetMissingSteps'] } historyGetMissingSteps\n * @property { CollaborationPlugin['onExternalHistorySteps'] } onExternalHistorySteps\n * @property { CollaborationPlugin['resetFromSteps'] } resetFromSteps\n * @property { CollaborationPlugin['setInitialBranchStepId'] } setInitialBranchStepId\n */\n\n/**\n * @typedef {(() => void)[]} external_history_step_handlers\n */\n\nexport class CollaborationPlugin extends Plugin {\n    static id = \"collaboration\";\n    static dependencies = [\"history\", \"selection\", \"sanitize\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        history_cleaned_handlers: this.onHistoryClean.bind(this),\n        history_reset_handlers: this.onHistoryReset.bind(this),\n        step_added_handlers: ({ step }) => this.onStepAdded(step),\n\n        /** Overrides */\n        set_attribute_overrides: this.setAttribute.bind(this),\n\n        history_step_processors: this.processHistoryStep.bind(this),\n        unreversible_step_predicates: this.isUnreversibleStep.bind(this),\n    };\n    static shared = [\n        \"getBranchIds\",\n        \"getSnapshotSteps\",\n        \"historyGetMissingSteps\",\n        \"onExternalHistorySteps\",\n        \"resetFromSteps\",\n        \"setInitialBranchStepId\",\n    ];\n\n    /** @type { CollaborationPluginConfig['peerId'] } */\n    peerId = null;\n\n    setup() {\n        this.peerId = this.config.collaboration.peerId;\n        if (!this.peerId) {\n            throw new Error(\"The collaboration plugin requires a peerId\");\n        }\n        this._snapshotInterval = setInterval(() => {\n            this.makeSnapshot();\n        }, HISTORY_SNAPSHOT_INTERVAL);\n    }\n\n    destroy() {\n        super.destroy();\n        clearInterval(this._snapshotInterval);\n        this._snapshotInterval = false;\n    }\n\n    onHistoryClean() {\n        this.branchStepIds = [];\n    }\n    onHistoryReset() {\n        const firstStep = this.dependencies.history.getHistorySteps()[0];\n        this.snapshots = [{ step: firstStep }];\n    }\n    /**\n     * @param {HistoryStep} step\n     */\n    isUnreversibleStep(step) {\n        return step.peerId !== this.peerId;\n    }\n    /**\n     * @param {Node} node\n     * @param {string} attributeName\n     * @param {string} attributeValue\n     */\n    setAttribute(node, attributeName, attributeValue) {\n        if (attributeValue) {\n            this.safeSetAttribute(node, attributeName, attributeValue);\n            return true;\n        }\n    }\n\n    /**\n     * Get all the history ids for the current history branch.\n     */\n    getBranchIds() {\n        const steps = this.dependencies.history.getHistorySteps();\n        return (this.initialBranchStepId || \"\")\n            .split(\",\")\n            .concat(this.branchStepIds)\n            .concat(steps.map((s) => s.id));\n    }\n    /**\n     * Safely set an attribute on a node.\n     * @param {HTMLElement} node\n     * @param {string} attributeName\n     * @param {string} attributeValue\n     */\n    safeSetAttribute(node, attributeName, attributeValue) {\n        const clone = this.document.createElement(node.tagName);\n        clone.setAttribute(attributeName, attributeValue);\n        this.dependencies.sanitize.sanitize(clone);\n        if (clone.hasAttribute(attributeName)) {\n            node.setAttribute(attributeName, clone.getAttribute(attributeName));\n        } else {\n            node.removeAttribute(attributeName);\n        }\n    }\n\n    /**\n     * Apply external steps coming from the collaboration.\n     *\n     * @param {Object} newSteps External steps to be applied\n     */\n    onExternalHistorySteps(newSteps) {\n        let stepIndex = 0;\n        const selectionData = this.dependencies.selection.getSelectionData();\n\n        const steps = this.dependencies.history.getHistorySteps();\n        for (const newStep of newSteps) {\n            // todo: add a test that no 2 history_missing_parent_step_handlers\n            // are called in same stack.\n            const insertIndex = this.getInsertStepIndex(steps, newStep);\n            if (typeof insertIndex === \"undefined\") {\n                continue;\n            }\n            this.dependencies.history.addExternalStep(newStep, insertIndex);\n            stepIndex++;\n        }\n        if (selectionData.documentSelectionIsInEditable) {\n            this.dependencies.selection.rectifySelection(selectionData.editableSelection);\n        }\n\n        this.dispatchTo(\"external_history_step_handlers\");\n\n        // todo: ensure that if the selection was not in the editable before the\n        // reset, it remains where it was after applying the snapshot.\n\n        if (stepIndex) {\n            this.config.onChange?.();\n        }\n    }\n\n    /**\n     * @param {HistoryStep[]} steps\n     * @param {HistoryStep} newStep\n     */\n    getInsertStepIndex(steps, newStep) {\n        let index = steps.length - 1;\n        while (index >= 0 && steps[index].id !== newStep.previousStepId) {\n            // Skip steps that are already in the list.\n            if (steps[index].id === newStep.id) {\n                return;\n            }\n            index--;\n        }\n\n        // When the previousStepId is not present in the steps it\n        // could be either:\n        // - the previousStepId is before a snapshot of the same history\n        // - the previousStepId has not been received because peers were\n        //   disconnected at that time\n        // - the previousStepId is in another history (in case two totally\n        //   differents `steps` (but it should not arise)).\n        if (index < 0) {\n            const historySteps = steps;\n            let index = historySteps.length - 1;\n            // Get the last known step that we are sure the missing step\n            // peer has. It could either be a step that has the same\n            // peerId or the first step.\n            while (index !== 0) {\n                if (historySteps[index].peerId === newStep.peerId) {\n                    break;\n                }\n                index--;\n            }\n            const fromStepId = historySteps[index].id;\n            this.dispatchTo(\"history_missing_parent_step_handlers\", {\n                step: newStep,\n                fromStepId: fromStepId,\n            });\n            return;\n        }\n\n        let concurentSteps = [];\n        index++;\n        while (index < steps.length) {\n            if (steps[index].previousStepId === newStep.previousStepId) {\n                if (steps[index].id.localeCompare(newStep.id) === 1) {\n                    break;\n                } else {\n                    concurentSteps = [steps[index].id];\n                }\n            } else {\n                if (concurentSteps.includes(steps[index].previousStepId)) {\n                    concurentSteps.push(steps[index].id);\n                } else {\n                    break;\n                }\n            }\n            index++;\n        }\n\n        return index;\n    }\n\n    /**\n     * @param {Object} params\n     * @param {string} params.fromStepId\n     * @param {string} [params.toStepId]\n     */\n    historyGetMissingSteps({ fromStepId, toStepId }) {\n        const steps = this.dependencies.history.getHistorySteps();\n        const fromIndex = steps.findIndex((x) => x.id === fromStepId);\n        const toIndex = toStepId ? steps.findIndex((x) => x.id === toStepId) : steps.length;\n        if (fromIndex === -1 || toIndex === -1) {\n            return -1;\n        }\n        return steps.slice(fromIndex + 1, toIndex);\n    }\n\n    getSnapshotSteps() {\n        const historySteps = this.dependencies.history.getHistorySteps();\n        // If the current snapshot has no time, it means that there is the no\n        // other snapshot that have been made (either it is the one created upon\n        // initialization or reseted by history's resetFromSteps).\n        if (!this.snapshots[0].time) {\n            return { steps: historySteps, historyIds: this.getBranchIds() };\n        }\n        const snapshotSteps = [];\n        let snapshot;\n        if (this.snapshots[0].time + HISTORY_SNAPSHOT_BUFFER_TIME < Date.now()) {\n            snapshot = this.snapshots[0];\n        } else {\n            // this.snapshots[1] has being created at least 1 minute ago\n            // (HISTORY_SNAPSHOT_INTERVAL) or it is the first step.\n            snapshot = this.snapshots[1];\n        }\n        let index = historySteps.length - 1;\n        while (historySteps[index].id !== snapshot.step.id) {\n            snapshotSteps.push(historySteps[index]);\n            index--;\n        }\n        snapshotSteps.push(snapshot.step);\n        snapshotSteps.reverse();\n\n        return { steps: snapshotSteps, historyIds: this.getBranchIds() };\n    }\n    setInitialBranchStepId(stepId) {\n        this.initialBranchStepId = stepId;\n    }\n    resetFromSteps(steps, branchStepIds) {\n        this.dependencies.selection.resetSelection();\n        this.dependencies.history.resetFromSteps(steps);\n        this.snapshots = [{ step: steps[0] }];\n        this.branchStepIds = branchStepIds;\n\n        // @todo @phoenix: test that the hint are proprely handeled\n        // this._handleCommandHint();\n        // @todo @phoenix: make the multiselection\n        // this.multiselectionRefresh();\n        // @todo @phoenix: check it is still relevant\n        // this.dispatchEvent(new Event(\"resetFromSteps\"));\n    }\n\n    makeSnapshot() {\n        const historyLength = this.dependencies.history.getHistorySteps().length;\n        if (!this.lastSnapshotLength || this.lastSnapshotLength < historyLength) {\n            this.lastSnapshotLength = historyLength;\n            const step = this.dependencies.history.makeSnapshotStep();\n            const snapshot = {\n                time: Date.now(),\n                step: step,\n            };\n            this.snapshots = [snapshot, this.snapshots[0]];\n        }\n    }\n\n    /**\n     * @param {HistoryStep} step\n     */\n    onStepAdded(step) {\n        step.peerId = this.peerId;\n        this.dispatchTo(\"collaboration_step_added_handlers\", step);\n    }\n    /**\n     * @param {HistoryStep} step\n     */\n    processHistoryStep(step) {\n        step.peerId = this.peerId;\n        return step;\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestBlock, isBlock } from \"@html_editor/utils/blocks\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { user } from \"@web/core/user\";\n\n/**\n * @typedef {Object} SelectionInfo\n * @property {import(\"@html_editor/core/history_plugin\").SerializedSelection} selection\n * @property {string} color\n * @property {string} peerId\n * @property {string} peerName\n * @property {string} avatarPositionKey\n * @property {HTMLElement} avatarElement\n * @property {HTMLElement} avatarTargetElement\n */\n\nexport const AVATAR_SIZE = 25;\n\nexport class CollaborationSelectionAvatarPlugin extends Plugin {\n    static id = \"collaborationSelectionAvatar\";\n    static dependencies = [\"history\", \"position\", \"localOverlay\", \"collaborationOdoo\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        collaboration_notification_handlers: this.handleCollaborationNotification.bind(this),\n        external_history_step_handlers: this.refreshSelection.bind(this),\n        layout_geometry_change_handlers: this.refreshSelection.bind(this),\n        set_movable_element_handlers: this.disableAvatarForElement.bind(this),\n        unset_movable_element_handlers: this.enableAvatars.bind(this),\n        collaborative_selection_update_handlers: this.updateSelection.bind(this),\n\n        collaboration_peer_metadata_providers: () => ({ avatarUrl: this.avatarUrl }),\n    };\n\n    /** @type {Map<string, SelectionInfo>} */\n    selectionInfos = new Map();\n\n    setup() {\n        this.avatarOverlay = this.dependencies.localOverlay.makeLocalOverlay(\"oe-avatars-overlay\");\n        this.avatarsCountersOverlay = this.dependencies.localOverlay.makeLocalOverlay(\n            \"oe-avatars-counters-overlay\"\n        );\n        this.avatarUrl = `${\n            browser.location.origin\n        }/web/image?model=res.users&field=avatar_128&id=${encodeURIComponent(user.userId)}`;\n    }\n    handleCollaborationNotification({ notificationName, notificationPayload }) {\n        switch (notificationName) {\n            case \"ptp_remove\":\n                this.selectionInfos.delete(notificationPayload);\n                this.refreshSelection();\n        }\n    }\n\n    /**\n     * @param {import(\"./collaboration_odoo_plugin\").CollaborationSelection} selection\n     */\n    updateSelection(selection) {\n        /** @type {SelectionInfo} */\n        const savedSelection = this.selectionInfos.get(selection.peerId) || {};\n        const newSelection = Object.assign(savedSelection, selection);\n        this.selectionInfos.set(selection.peerId, newSelection);\n        this.drawPeerAvatar(newSelection);\n        this.updateAvatarCounters();\n    }\n    /**\n     * @param {SelectionInfo} selectionInfo\n     */\n    drawPeerAvatar(selectionInfo) {\n        const { selection, peerId } = selectionInfo;\n        const peerMetadata = this.dependencies.collaborationOdoo.getPeerMetadata(peerId);\n        if (!peerMetadata) {\n            return;\n        }\n        const { avatarUrl, peerName = _t(\"Anonymous\") } = peerMetadata;\n        const anchorNode = this.dependencies.history.getNodeById(selection.anchorNodeId);\n        const focusNode = this.dependencies.history.getNodeById(selection.focusNodeId);\n        if (!anchorNode || !focusNode || !anchorNode.isConnected || !focusNode.isConnected) {\n            return;\n        }\n        const anchorBlock =\n            closestElement(anchorNode, (el) => isBlock(el) && el.parentElement === this.editable) ||\n            closestBlock(anchorNode);\n        if (!anchorBlock) {\n            return;\n        }\n\n        const containerRect = this.avatarOverlay.getBoundingClientRect();\n\n        // Draw user avatar.\n        let avatarElement = selectionInfo.avatarElement;\n        if (!avatarElement) {\n            avatarElement = this.document.createElement(\"div\");\n            avatarElement.className = \"oe-collaboration-caret-avatar\";\n            avatarElement.style.display = \"none\";\n            const image = this.document.createElement(\"img\");\n            avatarElement.append(image);\n            image.onload = () => avatarElement.style.removeProperty(\"display\");\n            image.setAttribute(\"src\", avatarUrl);\n            image.classList.add(\"object-fit-cover\");\n        }\n        // Avoid re-appending the element in the dom.\n        if (!avatarElement.parentElement) {\n            this.avatarOverlay.append(avatarElement);\n        }\n        // Make sure data is up to date.\n        selectionInfo.avatarElement = avatarElement;\n        selectionInfo.peerName = peerName;\n        selectionInfo.avatarTargetElement = anchorBlock;\n        this.selectionInfos.set(peerId, selectionInfo);\n\n        const anchorBlockRect = anchorBlock.getBoundingClientRect();\n        const top = anchorBlockRect.y - containerRect.y;\n        avatarElement.style.top = top + \"px\";\n        const closestList = closestElement(anchorNode, \"ul, ol\"); // Prevent overlap bullets.\n        const anchorX = closestList ? closestList.getBoundingClientRect().x : anchorBlockRect.x;\n        const left = anchorX - containerRect.x - AVATAR_SIZE;\n        avatarElement.style.left = left + \"px\";\n        selectionInfo.avatarPositionKey = `${left}|${top}`;\n    }\n    updateAvatarCounters() {\n        const avatarsOverlaps = {};\n        for (const info of this.selectionInfos.values()) {\n            const key = info.avatarPositionKey;\n            avatarsOverlaps[key] = avatarsOverlaps[key] || new Set();\n            avatarsOverlaps[key].add(info);\n        }\n\n        // Render avatars overlap.\n        this.avatarsCountersOverlay.replaceChildren();\n        for (const [overlapKey, infos] of Object.entries(avatarsOverlaps)) {\n            const size = infos.size;\n            if (size > 1) {\n                const [left, top] = overlapKey.split(\"|\").map((n) => parseInt(n, 10));\n                const div = document.createElement(\"div\");\n                div.className = \"oe-overlapping-counter\";\n                div.style.left = left + 10 + \"px\";\n                div.style.top = top + 10 + \"px\";\n                div.innerText = size;\n                this.avatarsCountersOverlay.append(div);\n            }\n        }\n    }\n    refreshSelection() {\n        if (!this.selectionInfos.size) {\n            this.avatarOverlay.replaceChildren();\n        }\n        this.avatarsCountersOverlay.replaceChildren();\n        for (const selection of this.selectionInfos.values()) {\n            this.drawPeerAvatar(selection);\n        }\n        this.updateAvatarCounters();\n    }\n\n    disableAvatarForElement(element) {\n        this.enableAvatars();\n        for (const info of this.selectionInfos.values()) {\n            if (info.avatarTargetElement === element) {\n                if (!info.avatarElement.classList.contains(\"invisible\")) {\n                    info.avatarElement.classList.add(\"invisible\");\n                }\n            }\n        }\n    }\n    enableAvatars() {\n        for (const element of this.avatarOverlay.querySelectorAll(\n            \".oe-collaboration-caret-avatar.invisible\"\n        )) {\n            element.classList.remove(\"invisible\");\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport {\n    getDeepestPosition,\n    isProtected,\n    isProtecting,\n    isUnprotecting,\n} from \"@html_editor/utils/dom_info\";\nimport { childNodes } from \"@html_editor/utils/dom_traversal\";\nimport { DIRECTIONS } from \"@html_editor/utils/position\";\nimport { getCursorDirection } from \"@html_editor/utils/selection\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nexport class CollaborationSelectionPlugin extends Plugin {\n    static id = \"collaborationSelection\";\n    static dependencies = [\"history\", \"collaborationOdoo\", \"position\", \"localOverlay\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        collaboration_notification_handlers: this.handleCollaborationNotification.bind(this),\n        layout_geometry_change_handlers: this.refreshSelection.bind(this),\n        collaborative_selection_update_handlers: this.updateSelection.bind(this),\n\n        collaboration_peer_metadata_providers: () => ({ selectionColor: this.selectionColor }),\n    };\n    selectionInfos = new Map();\n\n    setup() {\n        this.selectionOverlay =\n            this.dependencies.localOverlay.makeLocalOverlay(\"oe-selections-container\");\n        this.selectionColor = `hsl(${(Math.random() * 360).toFixed(0)}, 75%, 50%)`;\n    }\n    handleCollaborationNotification({ notificationName, notificationPayload }) {\n        switch (notificationName) {\n            case \"ptp_remove\":\n                this.multiselectionRemove(notificationPayload);\n                this.selectionInfos.delete(notificationPayload);\n                break;\n        }\n    }\n    /**\n     * @param {import(\"./collaboration_odoo_plugin\").CollaborationSelection} selection\n     */\n    updateSelection(selection) {\n        this.selectionInfos.set(selection.peerId, selection);\n        this.drawPeerSelection(selection);\n    }\n    /**\n     * @param {import(\"./collaboration_odoo_plugin\").CollaborationSelection} selection\n     */\n    drawPeerSelection({ selection, peerId }) {\n        const peerMetadata = this.dependencies.collaborationOdoo.getPeerMetadata(peerId);\n        if (!peerMetadata) {\n            return;\n        }\n        const { selectionColor, peerName = _t(\"Anonymous\") } = peerMetadata;\n        this.multiselectionRemove(peerId);\n        let clientRects;\n\n        let anchorNode = this.dependencies.history.getNodeById(selection.anchorNodeId);\n        let focusNode = this.dependencies.history.getNodeById(selection.focusNodeId);\n        let anchorOffset = selection.anchorOffset;\n        let focusOffset = selection.focusOffset;\n        if (!anchorNode || !focusNode) {\n            anchorNode = this.editable.children[0];\n            focusNode = this.editable.children[0];\n            anchorOffset = 0;\n            focusOffset = 0;\n        }\n        const anchorTarget = childNodes(anchorNode).at(anchorOffset);\n        const focusTarget = childNodes(focusNode).at(focusOffset);\n        const protectionCheck = (node) =>\n            isProtecting(node) || (isProtected(node) && !isUnprotecting(node));\n        if (protectionCheck(anchorTarget) || protectionCheck(focusTarget)) {\n            // TODO @phoenix, TODO ABD: better handle collaborative selection\n            // on protected elements.\n            return;\n        }\n        if (anchorNode.isConnected && focusNode.isConnected) {\n            [anchorNode, anchorOffset] = getDeepestPosition(anchorNode, anchorOffset);\n            [focusNode, focusOffset] = getDeepestPosition(focusNode, focusOffset);\n        } else {\n            // todo: We should not be able to get here, this fixes multiples\n            // issues where we temporarily try to draw a an impossible\n            // selection. We should investigate the root cause of this issue.\n            anchorNode = this.editable.children[0];\n            focusNode = this.editable.children[0];\n            anchorOffset = 0;\n            focusOffset = 0;\n        }\n\n        const direction = getCursorDirection(anchorNode, anchorOffset, focusNode, focusOffset);\n        const range = new Range();\n        try {\n            if (direction === DIRECTIONS.RIGHT) {\n                range.setStart(anchorNode, anchorOffset);\n                range.setEnd(focusNode, focusOffset);\n            } else {\n                range.setStart(focusNode, focusOffset);\n                range.setEnd(anchorNode, anchorOffset);\n            }\n\n            clientRects = Array.from(range.getClientRects());\n        } catch {\n            // Changes in the dom might prevent the range to be instantiated\n            // (because of a removed node for example), in which case we ignore\n            // the range.\n            clientRects = [];\n        }\n        if (!clientRects.length) {\n            return;\n        }\n\n        // Draw rects (in case the selection is not collapsed).\n        const containerRect = this.selectionOverlay.getBoundingClientRect();\n        const indicators = clientRects.map(({ x, y, width, height }) => {\n            const rectElement = this.document.createElement(\"div\");\n            rectElement.style = `\n                position: absolute;\n                top: ${y - containerRect.y}px;\n                left: ${x - containerRect.x}px;\n                width: ${width}px;\n                height: ${height}px;\n                background-color: ${selectionColor};\n                opacity: 0.25;\n                pointer-events: none;\n            `;\n            rectElement.setAttribute(\"data-selection-peer-id\", peerId);\n            return rectElement;\n        });\n\n        // Draw carret.\n        const caretElement = this.document.createElement(\"div\");\n        caretElement.style = `border-left: 2px solid ${selectionColor}; position: absolute;`;\n        caretElement.setAttribute(\"data-selection-peer-id\", peerId);\n        caretElement.className = \"oe-collaboration-caret\";\n\n        // Draw carret top square.\n        const caretTopSquare = this.document.createElement(\"div\");\n        caretTopSquare.className = \"oe-collaboration-caret-top-square\";\n        caretTopSquare.style[\"background-color\"] = selectionColor;\n        caretTopSquare.setAttribute(\"data-peer-name\", peerName);\n        caretElement.append(caretTopSquare);\n\n        if (direction === DIRECTIONS.LEFT) {\n            const rect = clientRects[0];\n            caretElement.style.height = `${rect.height * 1.2}px`;\n            caretElement.style.top = `${rect.y - containerRect.y}px`;\n            caretElement.style.left = `${rect.x - containerRect.x}px`;\n        } else {\n            const rect = clientRects.at(-1);\n            caretElement.style.height = `${rect.height * 1.2}px`;\n            caretElement.style.top = `${rect.y - containerRect.y}px`;\n            caretElement.style.left = `${rect.right - containerRect.x}px`;\n        }\n        this.selectionOverlay.append(caretElement, ...indicators);\n    }\n\n    multiselectionRemove(peerId) {\n        const elements = this.selectionOverlay.querySelectorAll(\n            `[data-selection-peer-id=\"${peerId}\"]`\n        );\n        for (const element of elements) {\n            element.remove();\n        }\n    }\n    refreshSelection() {\n        this.selectionOverlay.replaceChildren();\n        for (const selection of this.selectionInfos.values()) {\n            this.drawPeerSelection(selection);\n        }\n    }\n}\n", "import {\n    applyObjectPropertyDifference,\n    getEmbeddedProps,\n    StateChangeManager,\n} from \"@html_editor/others/embedded_component_utils\";\nimport { Component, useState, useRef, onMounted, onWillDestroy } from \"@odoo/owl\";\n\nexport class EmbeddedCaptionComponent extends Component {\n    static template = \"html_editor.EmbeddedCaption\";\n\n    static props = {\n        image: { type: Element },\n        onUpdateCaption: { type: Function },\n        onEditorHistoryApply: { type: Function },\n        focusInput: { type: Boolean },\n        host: { type: Object },\n    };\n\n    setup() {\n        super.setup();\n        this.state = useState({\n            caption: \"\",\n            host: this.props.host,\n        });\n        this.captionInput = useRef(\"captionInput\");\n        if (this.props.focusInput) {\n            onMounted(() => {\n                this.captionInput.el.focus();\n            });\n        }\n        // Ensure the state, the attribute and the placeholder are in sync.\n        this.updateCaption();\n        const observer = new MutationObserver((mutations) => {\n            for (const mutation of mutations) {\n                if (mutation.type === \"attributes\" && mutation.attributeName === \"data-caption\") {\n                    this.updateCaption();\n                }\n            }\n        });\n        observer.observe(this.props.image, { attributes: true });\n        onWillDestroy(() => {\n            observer.disconnect();\n        });\n    }\n\n    updateCaption(caption = this.props.image.getAttribute(\"data-caption\")) {\n        if (caption !== this.state.caption) {\n            this.state.caption = caption;\n            this.props.onUpdateCaption(caption);\n        }\n    }\n\n    onInputBlur() {\n        // This is triggered before the selection changes. Wait before updating\n        // so when the history step triggers a normalization, it restores that\n        // new selection and not the old one.\n        setTimeout(() => {\n            if (this.captionInput.el) {\n                this.updateCaption(this.captionInput.el.value || \"\");\n            }\n        });\n    }\n\n    onInputKeyup(ev) {\n        if (ev.key === \"z\" && ev.ctrlKey && !this._appliedNativeHistory) {\n            this.props.onEditorHistoryApply(ev.shiftKey);\n        }\n        this._appliedNativeHistory = false;\n    }\n\n    onInputBeforeInput(ev) {\n        this._appliedNativeHistory = false;\n        if (ev.inputType === \"historyUndo\" || ev.inputType === \"historyRedo\") {\n            // Input elements handle their own history, but this event is not\n            // triggered if no changes were made to the input. So we handle the\n            // editor history on keyup in those cases, but let the browser do\n            // its thing otherwise.\n            this._appliedNativeHistory = true;\n        }\n    }\n}\n\nexport const captionEmbedding = {\n    name: \"caption\",\n    Component: EmbeddedCaptionComponent,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n    getStateChangeManager: (config) =>\n        new StateChangeManager(\n            Object.assign(config, {\n                propertyUpdater: {\n                    caption: (state, previous, next) => {\n                        applyObjectPropertyDifference(\n                            state,\n                            \"caption\",\n                            previous.caption,\n                            next.caption\n                        );\n                    },\n                },\n            })\n        ),\n};\n", "import {\n    applyObjectPropertyDifference,\n    getEmbeddedProps,\n    StateChangeManager,\n    useEmbeddedState,\n} from \"@html_editor/others/embedded_component_utils\";\nimport { useEffect, useRef, useState } from \"@odoo/owl\";\nimport { ReadonlyEmbeddedFileComponent } from \"@html_editor/others/embedded_components/core/file/readonly_file\";\n\nexport class EmbeddedFileComponent extends ReadonlyEmbeddedFileComponent {\n    static template = \"html_editor.EmbeddedFile\";\n\n    setup() {\n        super.setup();\n        // override the state by an embedded state.\n        this.state = useEmbeddedState(this.props.host);\n        this.fileModel.state = this.state;\n        this.localState = useState({\n            editFileName: false,\n        });\n        this.nameInput = useRef(\"nameInput\");\n        useEffect(\n            () => {\n                if (this.localState.editFileName) {\n                    this.nameInput.el.focus();\n                    this.nameInput.el.select();\n                }\n            },\n            () => [this.localState.editFileName]\n        );\n    }\n\n    onBlurNameInput(ev) {\n        this.localState.editFileName = false;\n        this.renameFile();\n    }\n\n    onFocusFileName(ev) {\n        this.localState.editFileName = true;\n    }\n\n    onKeydownNameInput(ev) {\n        if (ev.key !== \"Enter\") {\n            return;\n        } else {\n            ev.preventDefault();\n        }\n        if (this.renameFile()) {\n            this.localState.editFileName = false;\n            this.env.editorShared?.setSelectionAfter(this.props.host);\n        }\n    }\n\n    renameFile() {\n        let newName = this.nameInput.el.value;\n        if (!newName.length) {\n            return false;\n        }\n        if (newName === this.fileModel.filename) {\n            return true;\n        }\n        // filename is the name of the file as written in the editor by the\n        // user. It does not necessarily have the file extension.\n        this.fileModel.filename = newName;\n        if (this.fileModel.extension) {\n            const pattern = new RegExp(`\\\\.${this.fileModel.extension}$`, \"i\");\n            if (!newName.match(pattern)) {\n                newName += `.${this.fileModel.extension}`;\n            }\n        }\n        // name is the full name of the file (always with extension)\n        // and is used as the url queryParam when downloading it.\n        this.fileModel.name = newName;\n        return true;\n    }\n}\n\nexport const fileEmbedding = {\n    name: \"file\",\n    Component: EmbeddedFileComponent,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n    getStateChangeManager: (config) =>\n        new StateChangeManager(\n            Object.assign(config, {\n                propertyUpdater: {\n                    fileData: (state, previous, next) => {\n                        applyObjectPropertyDifference(\n                            state,\n                            \"fileData\",\n                            previous.fileData,\n                            next.fileData\n                        );\n                    },\n                },\n            })\n        ),\n};\n", "import { Component } from \"@odoo/owl\";\nimport { CopyButton } from \"@web/core/copy_button/copy_button\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\n\nexport const LANGUAGES = {\n    plaintext: \"Plain Text\",\n    markdown: \"Markdown\",\n    javascript: \"Javascript\",\n    typescript: \"Typescript\",\n    jsdoc: \"JSDoc\",\n    java: \"Java\",\n    python: \"Python\",\n    html: \"HTML\",\n    xml: \"XML\",\n    svg: \"SVG\",\n    json: \"JSON\",\n    css: \"CSS\",\n    sass: \"SASS\",\n    scss: \"SCSS\",\n    sql: \"SQL\",\n    diff: \"Diff\",\n};\n\nexport class CodeToolbar extends Component {\n    static template = \"html_editor.CodeToolbar\";\n    static props = {\n        target: { validate: (el) => el.nodeType === Node.ELEMENT_NODE },\n        getContent: { type: Function },\n        onLanguageChange: { type: Function },\n        currentLanguage: { type: String },\n    };\n    static components = { Dropdown, DropdownItem, CopyButton };\n\n    setup() {\n        super.setup();\n        this.languages = LANGUAGES;\n    }\n}\n", "import {\n    getEmbeddedProps,\n    StateChangeManager,\n    useEmbeddedState,\n} from \"@html_editor/others/embedded_component_utils\";\nimport { Component, onMounted, onWillStart, useEffect, useRef, useState } from \"@odoo/owl\";\nimport { loadBundle } from \"@web/core/assets\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport {\n    getPreValue,\n    highlightPre,\n} from \"../../core/syntax_highlighting/syntax_highlighting_utils\";\nimport { CodeToolbar } from \"./code_toolbar\";\n\nexport class EmbeddedSyntaxHighlightingComponent extends Component {\n    static template = \"html_editor.EmbeddedSyntaxHighlighting\";\n\n    static components = { CodeToolbar };\n    static props = {\n        value: { type: String },\n        languageId: { type: String },\n        onTextareaFocus: { type: Function },\n        host: { type: Object },\n    };\n\n    setup() {\n        super.setup();\n        this.state = useState({\n            host: this.props.host,\n            highlightedValue: \"\",\n        });\n        this.embeddedState = useEmbeddedState(this.props.host);\n        this.preRef = useRef(\"pre\");\n        this.textareaRef = useRef(\"textarea\");\n\n        onWillStart(() => this.loadPrism());\n        onMounted(() => {\n            this.pre = this.preRef.el;\n            this.textarea = this.textareaRef.el;\n            this.document = this.textarea.ownerDocument;\n            this.highlight();\n        });\n\n        useEffect(this.highlight.bind(this), () => [\n            this.embeddedState.value,\n            this.embeddedState.languageId,\n        ]);\n    }\n\n    /**\n     * Load the Prism library. This function exists only so it can be overridden\n     * in tests.\n     */\n    loadPrism() {\n        return loadBundle(\n            `html_editor.assets_prism${cookie.get(\"color_scheme\") === \"dark\" ? \"_dark\" : \"\"}`,\n            { targetDoc: this.props.host.ownerDocument }\n        );\n    }\n\n    /**\n     * Highlight the content of the pre.\n     */\n    highlight() {\n        const focus = this.document.activeElement === this.textarea;\n\n        highlightPre(this.pre, this.embeddedState.value, this.embeddedState.languageId);\n\n        // Ensure the values match.\n        const preValue = getPreValue(this.pre);\n        if (this.textarea.value !== preValue) {\n            this.textarea.value = preValue;\n        }\n        if (focus) {\n            this.textarea.focus({ preventScroll: true });\n            this.props.onTextareaFocus();\n        }\n        this.embeddedState.value = this.textarea.value;\n    }\n\n    onInput() {\n        this.textarea.focus();\n        this.props.onTextareaFocus();\n        this.embeddedState.value = this.textarea.value;\n    }\n\n    /**\n     * Handle tabulation in the textarea.\n     *\n     * @param {KeyboardEvent} ev\n     */\n    onKeydown(ev) {\n        if (ev.key === \"Tab\") {\n            ev.preventDefault();\n            const tabSize = +getComputedStyle(this.textarea).tabSize || 4;\n            const tab = \" \".repeat(tabSize);\n            const { selectionStart, selectionEnd } = this.textarea;\n            const collapsed = selectionStart === selectionEnd;\n            let start = this.textarea.value.slice(0, selectionStart).lastIndexOf(\"\\n\");\n            start = start === -1 ? 0 : start;\n            let newValue = \"\";\n            let spacesRemovedAtStart = 0;\n            if (ev.shiftKey) {\n                // Remove tabs.\n                let end = this.textarea.value\n                    .slice(selectionEnd, this.textarea.value.length)\n                    .indexOf(\"\\n\");\n                end = end === -1 ? 0 : end;\n                end = selectionEnd + end;\n                // From 0 to the last \\n before selection start.\n                newValue = this.textarea.value.slice(0, start);\n                // From the last \\n before selection start to selection end.\n                const regex = new RegExp(`(\\n|^)( |\\u00A0){1,${tabSize}}`, \"g\");\n                const startSlice = this.textarea.value.slice(start, selectionStart);\n                const cleanStartSlice = startSlice.replace(regex, \"$1\");\n                spacesRemovedAtStart = startSlice.length - cleanStartSlice.length;\n                newValue += cleanStartSlice;\n                newValue += this.textarea.value\n                    .slice(selectionStart, selectionEnd)\n                    .replace(regex, \"$1\");\n                newValue += this.textarea.value.slice(selectionEnd, end).replace(regex, \"$1\");\n                // From selection end to end.\n                newValue += this.textarea.value.slice(end, this.textarea.value.length);\n            } else {\n                // Insert tabs.\n                if (collapsed && /\\S/.test(this.textarea.value.slice(start, selectionStart))) {\n                    newValue =\n                        this.textarea.value.slice(0, selectionStart) +\n                        tab +\n                        this.textarea.value.slice(selectionStart, this.textarea.value.length);\n                } else {\n                    // From 0 to the last \\n before selection start.\n                    newValue = start ? this.textarea.value.slice(0, start) : tab;\n                    // From the last \\n before selection start to selection end.\n                    newValue += this.textarea.value\n                        .slice(start, selectionEnd)\n                        .replaceAll(\"\\n\", `\\n${tab}`);\n                    // From selection end to end.\n                    newValue += this.textarea.value.slice(selectionEnd, this.textarea.value.length);\n                }\n            }\n            const insertedChars = newValue.length - this.textarea.value.length;\n            this.textarea.value = newValue;\n            const newStart = selectionStart + (ev.shiftKey ? -spacesRemovedAtStart : tabSize);\n            const newEnd = collapsed ? newStart : selectionEnd + insertedChars;\n            this.textarea.setSelectionRange(newStart, newEnd, this.textarea.selectionDirection);\n            this.embeddedState.value = this.textarea.value;\n        }\n    }\n\n    /**\n     * Ensure the pre and textarea's scrolls match so they remain aligned.\n     */\n    onScroll() {\n        this.pre.scrollTop = this.textarea.scrollTop;\n        this.pre.scrollLeft = this.textarea.scrollLeft;\n    }\n\n    /**\n     * Change the language when selecting a new one via the code toolbar.\n     *\n     * @param {string} languageId\n     */\n    onLanguageChange(languageId) {\n        if (languageId && this.embeddedState.languageId !== languageId) {\n            this.textarea.focus();\n            this.props.onTextareaFocus();\n            this.embeddedState.languageId = languageId;\n        }\n    }\n}\n\nexport const syntaxHighlightingEmbedding = {\n    name: \"syntaxHighlighting\",\n    Component: EmbeddedSyntaxHighlightingComponent,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n    getStateChangeManager: (config) => new StateChangeManager(config),\n};\n", "import {\n    getEmbeddedProps,\n    StateChangeManager,\n    useEmbeddedState,\n} from \"@html_editor/others/embedded_component_utils\";\nimport { getVideoUrl } from \"@html_editor/utils/url\";\nimport {\n    Component,\n    onMounted,\n    onWillDestroy,\n    onWillUnmount,\n    useExternalListener,\n    useRef,\n} from \"@odoo/owl\";\nimport { Dropdown } from \"@web/core/dropdown/dropdown\";\nimport { useDropdownState } from \"@web/core/dropdown/dropdown_hooks\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { ReadonlyEmbeddedVideoComponent } from \"../../core/video/readonly_video\";\n\nexport class EmbeddedVideoComponent extends ReadonlyEmbeddedVideoComponent {\n    static template = \"html_editor.EmbeddedVideo\";\n    static props = {\n        platform: { type: String },\n        videoId: { type: String },\n        params: { type: Object, optional: true },\n        host: { type: HTMLElement },\n        createOverlay: { type: Function, optional: true },\n        focusEditable: { type: Function, optional: true },\n        addStep: { type: Function, optional: true },\n        openVideoSelectorDialog: { type: Function, optional: true },\n    };\n\n    setup() {\n        super.setup();\n        this.videoBlock = this.props.host;\n        this.state = useEmbeddedState(this.videoBlock);\n        this.dropdown = useDropdownState();\n\n        this.videoSettingsOverlay = this.props.createOverlay(VideoSettings, {\n            positionOptions: {\n                position: \"right-start\",\n            },\n            className: \"video-overlay\",\n            closeOnPointerdown: false,\n        });\n        this.iframeRef = useRef(\"iframeRef\");\n\n        useExternalListener(this.videoBlock, \"pointerenter\", () => {\n            this.videoSettingsOverlay.open({\n                target: this.videoBlock,\n                props: {\n                    videoBlock: this.videoBlock,\n                    overlay: this.videoSettingsOverlay,\n                    replaceVideo: () => {\n                        this.props.openVideoSelectorDialog((media) => {\n                            this.replaceVideo(media);\n                        }, this.iframeRef.el);\n                    },\n                    removeVideo: () => {\n                        this.videoBlock.remove();\n                        this.props.addStep();\n                    },\n                    focusEditable: this.props.focusEditable,\n                    dropdown: this.dropdown,\n                },\n            });\n        });\n\n        useExternalListener(this.videoBlock, \"pointerleave\", (e) => {\n            if (this.dropdown.isOpen || e.relatedTarget?.closest(\".video-overlay\")) {\n                return;\n            }\n            this.videoSettingsOverlay.close();\n        });\n\n        onWillDestroy(() => {\n            this.videoSettingsOverlay?.close();\n        });\n    }\n\n    get url() {\n        return getVideoUrl(this.state.platform, this.state.videoId, this.state.params).toString();\n    }\n\n    /**\n     * Replace a video in the editor\n     * @param {Object} media\n     */\n    replaceVideo(media) {\n        this.state.videoId = media.videoId;\n        this.state.platform = media.platform;\n        this.state.params = media.params;\n        this.props.focusEditable();\n    }\n}\n\nexport const videoEmbedding = {\n    name: \"video\",\n    Component: EmbeddedVideoComponent,\n    getProps: (host) => ({ host, ...getEmbeddedProps(host) }),\n    getStateChangeManager: (config) => new StateChangeManager(config),\n};\n\nexport class VideoSettings extends Component {\n    static template = \"html_editor.VideoSettings\";\n    static components = { Dropdown, DropdownItem };\n    static props = {\n        videoBlock: { type: HTMLElement },\n        overlay: { type: Object },\n        replaceVideo: { type: Function },\n        removeVideo: { type: Function },\n        focusEditable: { type: Function },\n        dropdown: { type: Object },\n    };\n\n    setup() {\n        this.menuRef = useRef(\"menuRef\");\n\n        onMounted(() => {\n            this.menuRef.el.addEventListener(\"pointerleave\", () => {\n                if (!this.props.dropdown.isOpen) {\n                    this.props.overlay.close();\n                }\n            });\n        });\n\n        useExternalListener(document, \"pointerdown\", (ev) => {\n            if (this.props.dropdown.isOpen) {\n                return;\n            }\n            this.props.overlay.close();\n        });\n\n        onWillUnmount(() => {\n            if (!this.props.videoBlock.isConnected) {\n                this.props.focusEditable();\n            }\n        });\n    }\n}\n", "import { fileEmbedding } from \"@html_editor/others/embedded_components/backend/file/file\";\nimport { captionEmbedding } from \"@html_editor/others/embedded_components/backend/caption/caption\";\nimport { readonlyFileEmbedding } from \"@html_editor/others/embedded_components/core/file/readonly_file\";\nimport {\n    readonlyTableOfContentEmbedding,\n    tableOfContentEmbedding,\n} from \"@html_editor/others/embedded_components/core/table_of_content/table_of_content\";\nimport { toggleBlockEmbedding } from \"@html_editor/others/embedded_components/core/toggle_block/toggle_block\";\nimport { videoEmbedding } from \"@html_editor/others/embedded_components/backend/video/video\";\nimport { readonlyVideoEmbedding } from \"@html_editor/others/embedded_components/core/video/readonly_video\";\nimport { syntaxHighlightingEmbedding } from \"@html_editor/others/embedded_components/backend/syntax_highlighting/syntax_highlighting\";\nimport { readonlySyntaxHighlightingEmbedding } from \"./core/syntax_highlighting/readonly_syntax_highlighting\";\n\nexport const MAIN_EMBEDDINGS = [\n    fileEmbedding,\n    tableOfContentEmbedding,\n    toggleBlockEmbedding,\n    videoEmbedding,\n    captionEmbedding,\n    syntaxHighlightingEmbedding,\n];\n\nexport const READONLY_MAIN_EMBEDDINGS = [\n    readonlyFileEmbedding,\n    readonlyTableOfContentEmbedding,\n    toggleBlockEmbedding,\n    readonlyVideoEmbedding,\n    captionEmbedding,\n    readonlySyntaxHighlightingEmbedding,\n];\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { closestBlock, isBlock } from \"@html_editor/utils/blocks\";\nimport { renderToElement } from \"@web/core/utils/render\";\nimport { unwrapContents } from \"@html_editor/utils/dom\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\nimport { EDITABLE_MEDIA_CLASS, isVisible } from \"@html_editor/utils/dom_info\";\nimport { boundariesOut, rightPos } from \"@html_editor/utils/position\";\nimport { findInSelection } from \"@html_editor/utils/selection\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport class CaptionPlugin extends Plugin {\n    static id = \"caption\";\n    static dependencies = [\n        \"image\",\n        \"split\",\n        \"history\",\n        \"embeddedComponents\",\n        \"selection\",\n        \"baseContainer\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"toggleImageCaption\",\n                title: _t(\"Add/remove a caption\"),\n                run: this.toggleImageCaption.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        toolbar_items: [\n            {\n                id: \"image_caption\",\n                description: _t(\"Add/remove a caption\"),\n                groupId: \"image_description\",\n                commandId: \"toggleImageCaption\",\n                text: \"Caption\",\n                isActive: () => this.hasImageCaption(this.dependencies.image.getTargetedImage()),\n            },\n        ],\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        mount_component_handlers: this.setupNewCaption.bind(this),\n        delete_handlers: this.afterDelete.bind(this),\n        delete_image_overrides: this.handleDeleteImage.bind(this),\n        after_save_media_dialog_handlers: this.onImageReplaced.bind(this),\n        hints: [{ selector: \"FIGCAPTION\", text: _t(\"Write a caption...\") }],\n        unsplittable_node_predicates: [\n            (node) => [\"FIGURE\", \"FIGCAPTION\"].includes(node.nodeName), // avoid merge\n        ],\n        image_name_predicates: [this.getImageName.bind(this)],\n        link_compatible_selection_predicates: [this.isLinkAllowedOnSelection.bind(this)],\n        // Consider a <figure> element as empty if it only contains a\n        // <figcaption> element (e.g. when its image has just been\n        // removed).\n        empty_node_predicates: (el) =>\n            el.matches?.(\"figure\") &&\n            el.children.length === 1 &&\n            el.children[0].matches(\"figcaption\"),\n        move_node_whitelist_selectors: \"figure\",\n    };\n\n    setup() {\n        for (const figure of this.editable.querySelectorAll(\"figure\")) {\n            // Embed the captions.\n            const image = figure.querySelector(\"img\");\n            figure.before(image);\n            const caption = figure.querySelector(\"figcaption\")?.textContent;\n            figure.remove();\n            this.addImageCaption(image, caption, false);\n        }\n    }\n\n    cleanForSave({ root }) {\n        for (const figure of root.querySelectorAll(\"figure\")) {\n            figure.removeAttribute(\"contenteditable\");\n            const image = figure.querySelector(\"img\");\n            // Remove embedding and convert caption attribute to text.\n            figure.querySelector(\"figcaption\").remove();\n            const caption = root.ownerDocument.createElement(\"figcaption\");\n            caption.textContent = image.getAttribute(\"data-caption\");\n            image.removeAttribute(\"data-caption\");\n            image.removeAttribute(\"data-caption-id\");\n            image.classList.remove(EDITABLE_MEDIA_CLASS);\n            image.after(caption);\n        }\n    }\n\n    hasImageCaption(image) {\n        if (!image) {\n            return;\n        }\n        const block = closestBlock(image);\n        return (\n            block.nodeName === \"FIGURE\" && !!block.querySelector(\"[data-embedded='caption'] input\")\n        );\n    }\n\n    toggleImageCaption(image = this.dependencies.image.getTargetedImage()) {\n        if (!image) {\n            return;\n        }\n        if (this.hasImageCaption(image)) {\n            this.removeImageCaption(image);\n        } else {\n            this.addImageCaption(image, image.getAttribute(\"data-caption\") || \"\");\n        }\n    }\n\n    getCaptionId() {\n        return \"\" + Math.floor(Math.random() * Date.now());\n    }\n\n    addImageCaption(image, captionText = \"\", focusInput = true) {\n        this.captionsBeingAdded ||= new Set();\n        // Move the image within a figure element.\n        const figure = this.document.createElement(\"figure\");\n        const link = image.parentElement.nodeName === \"A\" && image.parentElement;\n        if (link && (link.previousSibling || link.nextSibling)) {\n            // <p>wx<a><img/></a>yz</p> => <p>wx</p><p><a><img/></a></p><p>yz</p>\n            this.dependencies.split.splitAroundUntil(link, closestBlock(link));\n        } else if (\n            !link &&\n            (image.previousSibling || image.nextSibling) &&\n            closestBlock(image) !== this.editable\n        ) {\n            // <p>wx<img/>yz</p> => <p>wx</p><p><img/></p><p>yz</p>\n            const block = this.dependencies.split.splitAroundUntil(image, closestBlock(image));\n            if (isBlock(block.previousSibling) && !isVisible(block.previousSibling)) {\n                block.previousSibling.remove();\n            }\n            if (isBlock(block.nextSibling) && !isVisible(block.nextSibling)) {\n                block.nextSibling.remove();\n            }\n        }\n        // => <p><figure><img/></figure></p>\n        // or <p><a><figure><img/></figure></a></p>\n        image.before(figure);\n        figure.append(image);\n        if (!link && figure.parentElement !== this.editable) {\n            // => <figure><img/></figure></p>\n            // but still <p><a><figure><img/></figure></p>\n            unwrapContents(figure.parentElement);\n            // Figure is contenteditable=\"false\", so selection would jump\n            // to the nearest editable sibling <div>. Setting cursor at\n            // the end ensures caption input receives focus correctly.\n            this.dependencies.selection.setCursorEnd(figure);\n        }\n        // Set the caption and its ID.\n        const captionId = this.getCaptionId();\n        this.captionsBeingAdded.add(captionId);\n        image.setAttribute(\"data-caption-id\", captionId);\n        image.setAttribute(\"data-caption\", captionText || \"\");\n        // Ensure it's not possible to write inside the figure.\n        figure.setAttribute(\"contenteditable\", \"false\");\n        image.classList.add(EDITABLE_MEDIA_CLASS);\n        // Add the caption component.\n        // => <p><figure><img/><figcaption>...</figcaption></figure></p>\n        // or <p><a><figure><img/><figcaption>...</figcaption></figure></a></p>\n        const caption = renderToElement(\"html_editor.EmbeddedCaptionBlueprint\", {\n            embeddedProps: JSON.stringify({\n                id: captionId,\n                focusInput,\n            }),\n        });\n        figure.append(caption);\n        this.dependencies.history.addStep();\n        this.captionsBeingAdded.delete(captionId);\n    }\n\n    removeImageCaption(image) {\n        const figure = closestElement(image, \"figure\");\n        if (figure) {\n            figure.querySelector(\"figcaption\").remove();\n            if (closestBlock(figure.parentElement) === this.editable) {\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                if (figure.parentElement.nodeName === \"A\") {\n                    figure.parentElement.before(baseContainer);\n                    baseContainer.append(figure.parentElement);\n                } else {\n                    figure.before(baseContainer);\n                    baseContainer.append(figure);\n                }\n            }\n            unwrapContents(figure);\n            image.removeAttribute(\"data-caption-id\"); // (keep the data-caption for if we toggle again)\n            image.classList.remove(EDITABLE_MEDIA_CLASS);\n            // Select the image.\n            const [anchorNode, anchorOffset, focusNode, focusOffset] = boundariesOut(image);\n            this.dependencies.selection.setSelection({\n                anchorNode,\n                anchorOffset,\n                focusNode,\n                focusOffset,\n            });\n            this.dependencies.selection.focusEditable();\n            this.dependencies.history.addStep();\n        }\n    }\n\n    setupNewCaption({ name, props }) {\n        if (name === \"caption\") {\n            const id = props.id;\n            delete props.id;\n            const image = this.editable.querySelector(`img[data-caption-id=\"${id}\"]`);\n            Object.assign(props, {\n                image,\n                onUpdateCaption: (caption = \"\") => {\n                    const figcaption = image.parentElement.querySelector(\"figcaption\");\n                    if (figcaption && figcaption.getAttribute(\"placeholder\") !== caption) {\n                        // Adapt the figcaption element's placeholder to the new\n                        // caption for screen reader users.\n                        figcaption.setAttribute(\"placeholder\", caption);\n                    }\n                    if (caption !== image.getAttribute(\"data-caption\")) {\n                        image.setAttribute(\"data-caption\", caption);\n                    }\n                    if (!this.captionsBeingAdded?.has(id)) {\n                        // If the caption is being added, we update without\n                        // adding a history step because it will be added at the\n                        // end of adding the caption, by `addImageCaption`.\n                        this.dependencies.history.addStep();\n                    }\n                },\n                onEditorHistoryApply: (redo = false) => {\n                    if (redo) {\n                        this.dependencies.history.redo();\n                    } else {\n                        this.dependencies.history.undo();\n                    }\n                },\n            });\n        }\n    }\n\n    getImageName(image) {\n        if (closestElement(image, \"figure\")) {\n            return image.getAttribute(\"data-caption\");\n        }\n    }\n\n    isLinkAllowedOnSelection() {\n        const figure = findInSelection(\n            this.dependencies.selection.getEditableSelection(),\n            \"figure\"\n        );\n        if (\n            figure &&\n            this.dependencies.selection\n                .getTargetedNodes()\n                .every((node) => closestElement(node, \"figure\") === figure)\n        ) {\n            return true;\n        }\n    }\n\n    onImageReplaced(media) {\n        const figure = closestElement(media, \"figure\");\n        if (media.nodeName === \"IMG\" && figure) {\n            const [anchorNode, anchorOffset] = rightPos(figure);\n            const caption = figure.querySelector(\"[data-embedded='caption'] input\")?.value;\n            figure.before(media);\n            figure.remove();\n            this.addImageCaption(media, caption, false);\n            this.dependencies.selection.setSelection({ anchorNode, anchorOffset });\n        }\n    }\n\n    afterDelete() {\n        const { anchorNode } = this.dependencies.selection.getEditableSelection();\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        for (const figure of this.editable.querySelectorAll(\"figure:not(:has(img))\")) {\n            const isSelectionInFigure = targetedNodes.includes(figure) || anchorNode === figure;\n            const sibling = figure.nextSibling || figure.previousSibling;\n            figure.remove();\n            if (isSelectionInFigure) {\n                // Note: this assumes the selection is collapsed after delete.\n                this.dependencies.selection.setSelection({\n                    anchorNode: sibling,\n                    anchorOffset: 0,\n                });\n            }\n        }\n    }\n\n    handleDeleteImage(image) {\n        const figure = closestElement(image, \"figure\");\n        if (figure) {\n            const sibling = figure.nextSibling || figure.previousSibling;\n            figure.remove();\n            this.dependencies.selection.setSelection({\n                anchorNode: sibling,\n                anchorOffset: 0,\n            });\n            this.dependencies.history.addStep();\n            return true;\n        }\n    }\n}\n", "import { DocumentSelector } from \"@html_editor/main/media/media_dialog/document_selector\";\nimport { renderToElement } from \"@web/core/utils/render\";\n\n/**\n * Override the @see DocumentSelector to render the uploaded file as embedded\n * component with editable file name and previewable file.\n */\nexport class EmbeddedFileDocumentsSelector extends DocumentSelector {\n    static mediaSpecificClasses = [];\n\n    /** @override */\n    static async renderFileElement(attachment) {\n        return renderEmbeddedFileBox(attachment);\n    }\n}\n\n/**\n * @param {Object} attachment\n * @returns {Element}\n */\nexport function renderEmbeddedFileBox(attachment) {\n    const dotSplit = attachment.name.split(\".\");\n    const extension = dotSplit.length > 1 ? dotSplit.pop() : undefined;\n    const fileData = {\n        access_token: attachment.access_token,\n        checksum: attachment.checksum,\n        extension,\n        filename: attachment.name,\n        id: attachment.id,\n        mimetype: attachment.mimetype,\n        name: attachment.name,\n        type: attachment.type,\n        url: attachment.url || \"\",\n    };\n    return renderToElement(\"html_editor.EmbeddedFileBlueprint\", {\n        embeddedProps: JSON.stringify({ fileData }),\n    });\n}\n", "import { nextLeaf } from \"@html_editor/utils/dom_info\";\nimport { isBlock } from \"@html_editor/utils/blocks\";\nimport {\n    EmbeddedFileDocumentsSelector,\n    renderEmbeddedFileBox,\n} from \"./embedded_file_documents_selector\";\nimport { FilePlugin } from \"@html_editor/main/media/file_plugin\";\nimport { closestElement } from \"@html_editor/utils/dom_traversal\";\n\n/**\n * This plugin is meant to replace the File plugin.\n */\nexport class EmbeddedFilePlugin extends FilePlugin {\n    static id = \"embeddedFile\";\n    static dependencies = [...super.dependencies, \"embeddedComponents\", \"selection\"];\n\n    // Extends the base class resources\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        ...this.resources,\n        mount_component_handlers: this.setupNewFile.bind(this),\n    };\n\n    /** @override */\n    renderDownloadBox(attachment) {\n        return renderEmbeddedFileBox(attachment);\n    }\n\n    /** @override */\n    isUploadCommandAvailable({ anchorNode }) {\n        return (\n            super.isUploadCommandAvailable() &&\n            !closestElement(anchorNode, \"[data-embedded='clipboard']\")\n        );\n    }\n\n    /** @override */\n    get componentForMediaDialog() {\n        return EmbeddedFileDocumentsSelector;\n    }\n\n    setupNewFile({ name, env }) {\n        if (name === \"file\") {\n            Object.assign(env.editorShared, {\n                setSelectionAfter: (host) => {\n                    try {\n                        const leaf = nextLeaf(host, this.editable);\n                        if (!leaf) {\n                            return;\n                        }\n                        const leafEl = isBlock(leaf) ? leaf : leaf.parentElement;\n                        if (isBlock(leafEl) && leafEl.isContentEditable) {\n                            this.dependencies.selection.setSelection({\n                                anchorNode: leafEl,\n                                anchorOffset: 0,\n                            });\n                        }\n                    } catch {\n                        return;\n                    }\n                },\n            });\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { getEmbeddedProps } from \"@html_editor/others/embedded_component_utils\";\nimport {\n    DEFAULT_LANGUAGE_ID,\n    getPreValue,\n    newlinesToLineBreaks,\n} from \"../../core/syntax_highlighting/syntax_highlighting_utils\";\nimport { removeInvisibleWhitespace } from \"@html_editor/utils/dom\";\n\nconst CODE_BLOCK_CLASS = \"o_syntax_highlighting\";\nconst CODE_BLOCK_SELECTOR = `div.${CODE_BLOCK_CLASS}`;\n\nexport class SyntaxHighlightingPlugin extends Plugin {\n    static id = \"syntaxHighlighting\";\n    static dependencies = [\n        \"overlay\",\n        \"history\",\n        \"selection\",\n        \"protectedNode\",\n        \"embeddedComponents\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        // Ensure focus can be preserved within the textarea:\n        is_node_editable_predicates: (node) => {\n            if (node?.classList?.contains(\"o_prism_source\")) {\n                return true;\n            }\n        },\n        system_attributes: \"data-syntax-highlighting-autofocus\",\n\n        /** Handlers */\n        mount_component_handlers: this.setupNewCodeBlock.bind(this),\n        normalize_handlers: (root) => this.addCodeBlocks(root, true),\n        post_undo_handlers: () => this.addCodeBlocks(this.editable, true),\n        post_redo_handlers: () => this.addCodeBlocks(this.editable, true),\n        clean_for_save_handlers: withSequence(0, ({ root }) => this.cleanForSave(root)),\n        before_set_tag_handlers: (el, newTagName, cursors) => {\n            if (newTagName.toLowerCase() === \"pre\") {\n                // Remove invisible whitespace that would become visible in a `<pre>` element.\n                removeInvisibleWhitespace(el, cursors);\n            }\n        },\n\n        /** Processors */\n        clipboard_content_processors: (clonedContent) => this.cleanForSave(clonedContent),\n    };\n\n    setup() {\n        this.addCodeBlocks();\n    }\n\n    cleanForSave(root) {\n        for (const codeBlock of root.querySelectorAll(\"div.o_syntax_highlighting\")) {\n            // Save only the `<pre>` element, with information to rebuild the\n            // embedded component, so the saved DOM is independent of this plugin.\n            const pre = codeBlock.querySelector(\"pre\");\n            pre.dataset.embedded = \"readonlySyntaxHighlighting\"; // Make it work in readonly.\n            const embeddedProps = getEmbeddedProps(codeBlock);\n            const value = embeddedProps.value;\n            pre.dataset.languageId = embeddedProps.languageId;\n            codeBlock.before(pre);\n            codeBlock.remove();\n            // Remove highlighting.\n            pre.textContent = value;\n            newlinesToLineBreaks(pre);\n        }\n    }\n\n    /**\n     * Take all `<pre>` element in the given `root` that aren't in an embedded\n     * syntax highlighting block, and replace them with an embedded syntax\n     * highlighting block. If `preserveFocus` is true, set the currently\n     * targeted `<pre>` element to be focused.\n     *\n     * @param {Element} [root = this.editable]\n     * @param {boolean} [preserveFocus = false]\n     */\n    addCodeBlocks(root = this.editable, preserveFocus = false) {\n        const targetedNodes = this.dependencies.selection.getTargetedNodes();\n        const nonEmbeddedPres = [...root.querySelectorAll(\"pre\")].filter(\n            (pre) => !pre.closest(CODE_BLOCK_SELECTOR)\n        );\n        for (const pre of nonEmbeddedPres) {\n            const isPreInSelection = !targetedNodes.some((node) => !pre.contains(node));\n            const embeddedProps = JSON.stringify({\n                value: getPreValue(pre),\n                languageId: pre.dataset.languageId || DEFAULT_LANGUAGE_ID,\n            });\n            const codeBlock = this.dependencies.embeddedComponents.renderBlueprintToElement(\n                \"html_editor.EmbeddedSyntaxHighlightingBlueprint\",\n                { embeddedProps },\n                () => {\n                    if (preserveFocus && isPreInSelection) {\n                        const textarea = codeBlock.querySelector(\"textarea\");\n                        if (textarea !== codeBlock.ownerDocument.activeElement) {\n                            textarea.focus();\n                            this.dependencies.history.stageFocus();\n                        }\n                    }\n                }\n            );\n            pre.before(codeBlock);\n            if (isPreInSelection) {\n                // Removing the pre will make us lose the selection. The DOM\n                // would try to set it in the root, which would get corrected,\n                // preventing us from directly writing inside the textarea.\n                this.document.getSelection().removeAllRanges();\n            }\n            pre.remove();\n        }\n    }\n\n    setupNewCodeBlock({ name, props }) {\n        if (name === \"syntaxHighlighting\") {\n            Object.assign(props, {\n                onTextareaFocus: () => this.dependencies.history.stageFocus(),\n            });\n            props.host.removeAttribute(\"data-syntax-highlighting-autofocus\");\n        }\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { renderToElement } from \"@web/core/utils/render\";\nimport {\n    HEADINGS,\n    TableOfContentManager,\n} from \"@html_editor/others/embedded_components/core/table_of_content/table_of_content_manager\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\n\nexport class TableOfContentPlugin extends Plugin {\n    static id = \"tableOfContent\";\n    static dependencies = [\"dom\", \"selection\", \"embeddedComponents\", \"link\", \"history\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        user_commands: [\n            {\n                id: \"insertTableOfContent\",\n                title: _t(\"Table of Contents\"),\n                description: _t(\"Highlight the structure (headings)\"),\n                icon: \"fa-bookmark\",\n                run: this.insertTableOfContent.bind(this),\n                isAvailable: isHtmlContentSupported,\n            },\n        ],\n        powerbox_items: [\n            {\n                categoryId: \"navigation\",\n                commandId: \"insertTableOfContent\",\n            },\n        ],\n\n        /** Handlers */\n        restore_savepoint_handlers: () => this.delayedUpdateTableOfContents(this.editable),\n        history_reset_handlers: () => this.delayedUpdateTableOfContents(this.editable),\n        history_reset_from_steps_handlers: () => this.delayedUpdateTableOfContents(this.editable),\n        step_added_handlers: ({ stepCommonAncestor }) =>\n            this.delayedUpdateTableOfContents(stepCommonAncestor),\n        external_step_added_handlers: this.delayedUpdateTableOfContents.bind(this, this.editable),\n        clean_for_save_handlers: this.cleanForSave.bind(this),\n        mount_component_handlers: this.setupNewToc.bind(this),\n\n        system_classes: [\"o_embedded_toc_header_highlight\"],\n    };\n\n    setup() {\n        this.manager = new TableOfContentManager({\n            el: this.editable,\n        });\n        this.alive = true;\n    }\n\n    insertTableOfContent() {\n        const tableOfContentBlueprint = renderToElement(\"html_editor.TableOfContentBlueprint\");\n        this.dependencies.dom.insert(tableOfContentBlueprint);\n        this.dependencies.history.addStep();\n    }\n\n    /**\n     * @param {HTMLElement} root\n     */\n    cleanForSave({ root }) {\n        for (const el of root.querySelectorAll(\".o_embedded_toc_header_highlight\")) {\n            el.classList.remove(\"o_embedded_toc_header_highlight\");\n        }\n    }\n\n    destroy() {\n        super.destroy();\n        this.alive = false;\n    }\n\n    delayedUpdateTableOfContents(element) {\n        const selector = HEADINGS.join(\",\");\n        if (!(!element || element.querySelector(selector) || element.closest(selector))) {\n            return;\n        }\n        if (this.updateTimeout) {\n            window.clearTimeout(this.updateTimeout);\n        }\n        this.updateTimeout = window.setTimeout(() => {\n            if (!this.alive) {\n                return;\n            }\n            this.manager.updateStructure();\n        }, 500);\n    }\n\n    setupNewToc({ name, props }) {\n        if (name === \"tableOfContent\") {\n            Object.assign(props, {\n                manager: this.manager,\n            });\n        }\n    }\n}\n", "import { getEmbeddedProps } from \"@html_editor/others/embedded_component_utils\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { isHtmlContentSupported } from \"@html_editor/core/selection_plugin\";\nimport { baseContainerGlobalSelector } from \"@html_editor/utils/base_container\";\nimport { closestBlock } from \"@html_editor/utils/blocks\";\nimport { isEmptyBlock, isParagraphRelatedElement } from \"@html_editor/utils/dom_info\";\nimport {\n    childNodes,\n    children,\n    closestElement,\n    firstLeaf,\n    lastLeaf,\n    selectElements,\n} from \"@html_editor/utils/dom_traversal\";\nimport { parseHTML } from \"@html_editor/utils/html\";\nimport { childNodeIndex, nodeSize } from \"@html_editor/utils/position\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { renderToString } from \"@web/core/utils/render\";\nimport { uuid } from \"@web/core/utils/strings\";\n\nconst toggleSelector = \"[data-embedded='toggleBlock']\";\nconst titleSelector = \"[data-embedded-editable='title']\";\nconst contentSelector = \"[data-embedded-editable='content']\";\n\nexport class ToggleBlockPlugin extends Plugin {\n    static id = \"toggleBlock\";\n    static dependencies = [\n        \"baseContainer\",\n        \"delete\",\n        \"dom\",\n        \"embeddedComponents\", // toggle is an embedded component.\n        \"history\",\n        \"selection\",\n        \"split\",\n    ];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        hints: [\n            withSequence(20, {\n                selector: `${toggleSelector} ${titleSelector} > *`,\n                text: _t(\"Toggle title\"),\n            }),\n            withSequence(10, {\n                selector: `${toggleSelector} ${contentSelector}:not(:focus) > ${baseContainerGlobalSelector}:only-child`,\n                text: _t(\"Add something inside this toggle\"),\n            }),\n        ],\n        hint_targets_providers: (selectionData, editable) => [\n            ...editable.querySelectorAll(\n                `${toggleSelector} ${contentSelector} > ${baseContainerGlobalSelector}:only-child`\n            ),\n        ],\n        move_node_blacklist_selectors: `${toggleSelector} ${titleSelector} *`,\n        selection_blocker_predicates: (blocker) => {\n            // Prevent the insertion of selection placeholders around toggle blocks.\n            if (blocker.nodeType === Node.ELEMENT_NODE && blocker.dataset.embedded === \"toggleBlock\") {\n                return false;\n            }\n        },\n        powerbox_items: [\n            {\n                commandId: \"insertToggleBlock\",\n                categoryId: \"structure\",\n            },\n        ],\n        shortcuts: [{ hotkey: \"control+enter\", commandId: \"switchToggleBlockState\" }],\n        user_commands: [\n            {\n                id: \"insertToggleBlock\",\n                title: _t(\"Toggle list\"),\n                description: _t(\"Hide Text under foldable toggles\"),\n                icon: \"fa-caret-square-o-right\",\n                isAvailable: (selection) =>\n                    isHtmlContentSupported(selection) &&\n                    !closestElement(selection.anchorNode, `${toggleSelector} ${titleSelector}`),\n                run: () => {\n                    this.insertToggleBlock();\n                },\n            },\n            {\n                id: \"switchToggleBlockState\",\n                run: this.manageToggleFromTitle.bind(this),\n            },\n        ],\n\n        normalize_handlers: withSequence(Infinity, this.normalize.bind(this)),\n\n        delete_backward_overrides: this.handleDeleteBackward.bind(this),\n        delete_forward_overrides: this.handleDeleteForward.bind(this),\n        shift_tab_overrides: this.handleShiftTab.bind(this),\n        split_element_block_overrides: withSequence(1, this.handleSplitElementBlock.bind(this)),\n        tab_overrides: this.handleTab.bind(this),\n\n        power_buttons_visibility_predicates: this.showPowerButtons.bind(this),\n\n        before_insert_processors: this.handleInsert.bind(this),\n    };\n\n    setup() {\n        this.preventDeleteBackwardContentEnd = false;\n        this.selectedToggleEmptyContentSet = new Set();\n    }\n\n    explodeToggle(toggle) {\n        const title = toggle.querySelector(titleSelector);\n        const content = toggle.querySelector(contentSelector);\n        let contentChildren = children(content);\n        if (contentChildren.length === 1 && isEmptyBlock(contentChildren[0])) {\n            contentChildren = [];\n        }\n        toggle.replaceWith(...children(title), ...contentChildren);\n    }\n\n    forceToggle(toggle, { showContent, restoreSelection } = {}) {\n        toggle.dispatchEvent(\n            new CustomEvent(\"forceToggle\", { detail: { showContent, restoreSelection } })\n        );\n    }\n\n    generateUniqueIds(toggles) {\n        for (const toggle of toggles) {\n            const props = getEmbeddedProps(toggle);\n            props.toggleBlockId = this.getUniqueIdentifier();\n            toggle.dataset.embeddedProps = JSON.stringify(props);\n        }\n    }\n\n    getClosestToggleContentInfo(node) {\n        const toggle = closestElement(node, toggleSelector);\n        const title = toggle?.querySelector(titleSelector);\n        const content = toggle?.querySelector(contentSelector);\n        return content?.contains(node) ? { content, title, toggle } : {};\n    }\n\n    getClosestToggleTitleInfo(node) {\n        const toggle = closestElement(node, toggleSelector);\n        const title = toggle?.querySelector(titleSelector);\n        const content = toggle?.querySelector(contentSelector);\n        return title?.contains(node) ? { content, title, toggle } : {};\n    }\n\n    getToggleFromTitleSelection() {\n        const selection = this.dependencies.selection.getEditableSelection();\n        if (!selection.anchorNode) {\n            return;\n        }\n        const { toggle } = this.getClosestToggleTitleInfo(selection.anchorNode);\n        return toggle;\n    }\n\n    getUniqueIdentifier() {\n        return uuid();\n    }\n\n    /**\n     * Handle all behaviors linked to the use of deleteBackward in the editor:\n     * 1. selection at start of title: explode the toggle and keep title and content as siblings\n     * 2. selection at end of content in a paragraph: unwraps from the toggle content\n     * 3. selection at start of content in a paragraph: merge the paragraph with the title\n     * 4. selection at start of paragraph after a toggle: merge the paragraph at content end\n     */\n    handleDeleteBackward(range) {\n        for (const handler of [\n            this.handleDeleteBackwardTitleStart,\n            this.handleDeleteBackwardContentEnd,\n            this.handleDeleteBackwardContentStart,\n            this.handleDeleteBackwardAfterToggle,\n        ]) {\n            if (handler.call(this, range)) {\n                return true;\n            }\n        }\n    }\n\n    handleDeleteBackwardContentEnd({ endContainer, endOffset }) {\n        const block = closestBlock(endContainer);\n        const isEmptyContainer = isEmptyBlock(endContainer);\n        const leaf = isEmptyContainer ? endContainer : firstLeaf(block);\n        const { toggle, content } = this.getClosestToggleContentInfo(endContainer);\n        if (\n            !content ||\n            endOffset !== 0 ||\n            childNodeIndex(block) === 0 ||\n            block.nextElementSibling ||\n            leaf !== endContainer ||\n            !isParagraphRelatedElement(block) ||\n            this.preventDeleteBackwardContentEnd\n        ) {\n            return;\n        }\n        toggle.after(block);\n        this.dependencies.selection.setCursorStart(block);\n        return true;\n    }\n\n    handleDeleteBackwardContentStart({ endContainer, endOffset }) {\n        const block = closestBlock(endContainer);\n        const leaf = isEmptyBlock(endContainer) ? endContainer : firstLeaf(block);\n        const { title, content } = this.getClosestToggleContentInfo(endContainer);\n        if (\n            !content ||\n            endOffset !== 0 ||\n            childNodeIndex(block) !== 0 ||\n            leaf !== endContainer ||\n            !isParagraphRelatedElement(block)\n        ) {\n            return;\n        }\n        title.append(block);\n        this.dependencies.selection.setCursorStart(block);\n        this.dependencies.delete.deleteBackward(\n            this.dependencies.selection.getEditableSelection(),\n            \"character\"\n        );\n        return true;\n    }\n\n    handleDeleteBackwardTitleStart({ endContainer, endOffset }) {\n        const block = closestBlock(endContainer);\n        const leaf = isEmptyBlock(endContainer) ? endContainer : firstLeaf(block);\n        const { toggle, title } = this.getClosestToggleTitleInfo(endContainer);\n        if (!title || endOffset !== 0 || childNodeIndex(block) !== 0 || leaf !== endContainer) {\n            return;\n        }\n        const cursors = this.dependencies.selection.preserveSelection();\n        this.explodeToggle(toggle);\n        cursors.restore();\n        return true;\n    }\n\n    handleDeleteBackwardAfterToggle({ endContainer, endOffset }) {\n        const block = closestBlock(endContainer);\n        const leaf = isEmptyBlock(endContainer) ? endContainer : firstLeaf(block);\n        const toggle = block?.previousSibling;\n        if (!toggle?.matches?.(toggleSelector) || endOffset !== 0 || leaf !== endContainer) {\n            return;\n        }\n        let target = toggle.querySelector(contentSelector);\n        if (target.parentElement.matches(\".d-none\")) {\n            if (!isParagraphRelatedElement(block)) {\n                return;\n            }\n            const title = toggle.querySelector(titleSelector);\n            target = title;\n        }\n        target.append(block);\n        this.dependencies.selection.setCursorStart(block);\n        this.preventDeleteBackwardContentEnd = true;\n        this.dependencies.delete.deleteBackward(\n            this.dependencies.selection.getEditableSelection(),\n            \"character\"\n        );\n        this.preventDeleteBackwardContentEnd = false;\n        return true;\n    }\n\n    /**\n     * Handle all behaviors linked to the use of deleteForward in the editor:\n     * 1. selection at end of title:\n     *   - (optional) explode a potential toggle at the start of content\n     *   - merge first paragraph from content with the title\n     * 2. selection at end of content in a paragraph:\n     *   - (optional) explode a potential sibling toggle\n     *   - merge a sibling paragraph at content end\n     * 3. selection at end of paragraph before a toggle: explode the toggle\n     */\n    handleDeleteForward(range) {\n        for (const handler of [\n            this.handleDeleteForwardTitleEnd,\n            this.handleDeleteForwardContentEnd,\n            this.handleDeleteForwardBeforeToggle,\n        ]) {\n            if (handler.call(this, range)) {\n                return true;\n            }\n        }\n    }\n\n    handleDeleteForwardContentEnd({ startContainer, startOffset }) {\n        const block = closestBlock(startContainer);\n        const isEmptyContainer = isEmptyBlock(startContainer);\n        const leaf = isEmptyContainer ? startContainer : lastLeaf(block);\n        const { toggle, content } = this.getClosestToggleContentInfo(startContainer);\n        if (\n            !content ||\n            !(\n                (isEmptyContainer && startOffset === 0) ||\n                startOffset === nodeSize(startContainer)\n            ) ||\n            block === this.editable ||\n            block.nextElementSibling ||\n            leaf !== startContainer ||\n            !isParagraphRelatedElement(block)\n        ) {\n            return;\n        }\n        let nextEl = toggle.nextSibling;\n        if (nextEl?.matches?.(toggleSelector)) {\n            this.explodeToggle(nextEl);\n            nextEl = toggle.nextSibling;\n        }\n        if (!isParagraphRelatedElement(nextEl)) {\n            return;\n        }\n        content.append(nextEl);\n        this.dependencies.selection.setCursorEnd(block);\n        this.dependencies.delete.deleteForward(\n            this.dependencies.selection.getEditableSelection(),\n            \"character\"\n        );\n        return true;\n    }\n\n    handleDeleteForwardTitleEnd({ startContainer, startOffset }) {\n        const block = closestBlock(startContainer);\n        const isEmptyContainer = isEmptyBlock(startContainer);\n        const leaf = isEmptyContainer ? startContainer : lastLeaf(block);\n        const { toggle, title, content } = this.getClosestToggleTitleInfo(startContainer);\n        if (\n            !title ||\n            !(\n                (isEmptyContainer && startOffset === 0) ||\n                startOffset === nodeSize(startContainer)\n            ) ||\n            block === this.editable ||\n            block.nextElementSibling ||\n            leaf !== startContainer\n        ) {\n            return;\n        }\n        let nextEl;\n        if (content.parentElement.matches(\".d-none\")) {\n            nextEl = toggle.nextSibling;\n            if (nextEl.matches?.(toggleSelector)) {\n                this.explodeToggle(nextEl);\n                nextEl = toggle.nextSibling;\n            }\n        } else {\n            nextEl = content.firstChild;\n            if (nextEl.matches?.(toggleSelector)) {\n                this.explodeToggle(nextEl);\n                nextEl = content.firstChild;\n            }\n        }\n        if (!isParagraphRelatedElement(nextEl)) {\n            return;\n        }\n        title.append(nextEl);\n        this.dependencies.selection.setCursorEnd(block);\n        this.dependencies.delete.deleteForward(\n            this.dependencies.selection.getEditableSelection(),\n            \"character\"\n        );\n        return true;\n    }\n\n    handleDeleteForwardBeforeToggle({ startContainer, startOffset }) {\n        const block = closestBlock(startContainer);\n        const isEmptyContainer = isEmptyBlock(startContainer);\n        const leaf = isEmptyContainer ? startContainer : lastLeaf(block);\n        const toggle = block.nextSibling;\n        if (\n            !toggle?.matches?.(toggleSelector) ||\n            !(\n                (isEmptyContainer && startOffset === 0) ||\n                startOffset === nodeSize(startContainer)\n            ) ||\n            leaf !== startContainer\n        ) {\n            return;\n        }\n        if (isEmptyBlock(block)) {\n            block.remove();\n            const title = toggle.querySelector(titleSelector);\n            this.dependencies.selection.setCursorStart(title.firstElementChild);\n        } else {\n            this.explodeToggle(toggle);\n            this.dependencies.selection.setCursorEnd(block);\n            this.dependencies.delete.deleteForward(\n                this.dependencies.selection.getEditableSelection(),\n                \"character\"\n            );\n        }\n        return true;\n    }\n\n    /**\n     * Generate new toggleBlockIds for every inserted toggle, to avoid duplicating\n     * copies.\n     */\n    handleInsert(insertContainer) {\n        const insertedToggles = insertContainer.querySelectorAll(toggleSelector);\n        this.generateUniqueIds(insertedToggles);\n        return insertContainer;\n    }\n\n    /**\n     * Shift + Tab in a toggle title will extract it from a potential parent\n     * toggle, and every child of that parent toggle content that is a nextSibling\n     * of the current toggle will be appended to the current toggle content.\n     */\n    handleShiftTab() {\n        const toggle = this.getToggleFromTitleSelection();\n        if (toggle) {\n            const closestToggleAncestor = closestElement(toggle.parentElement, toggleSelector);\n            if (closestToggleAncestor) {\n                const cursors = this.dependencies.selection.preserveSelection();\n                const ancestorContent = closestToggleAncestor.querySelector(contentSelector);\n                const containerContent = toggle.querySelector(contentSelector);\n                const siblings = childNodes(ancestorContent).filter(\n                    (node) =>\n                        toggle.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING\n                );\n                if (isEmptyBlock(containerContent.lastElementChild)) {\n                    containerContent.lastElementChild.remove();\n                }\n                containerContent.append(...siblings);\n                closestToggleAncestor.after(toggle);\n                this.forceToggle(toggle, { showContent: true, restoreSelection: cursors.restore });\n                this.dependencies.history.addStep();\n            }\n            return true;\n        }\n    }\n\n    /**\n     * This method handles the behavior when the user presses the Enter key.\n     * In the editor, when the user presses the Enter key, this splits the focused text block into 2\n     * separate ones. When the text block is split then it calls to all handlers so that they can trigger\n     * a specific behavior with the split block.\n     *\n     * This handler handles multiple cases:\n     *      1. The toggle title is currently empty (remove toggle)\n     *      2. Cursor is at the start of the toggle title (create new toggle before)\n     *      3. Cursor elsewhere in the toggle title (create new toggle after)\n     * @param {Object} param @see SplitPlugin.splitElementBlock\n     * @returns true if indeed handled by the method\n     */\n    handleSplitElementBlock({ targetNode, targetOffset, blockToSplit }) {\n        const { toggle, title, content } = this.getClosestToggleTitleInfo(targetNode);\n        if (title) {\n            const selection = this.dependencies.selection.getEditableSelection();\n            if (isEmptyBlock(selection.anchorNode)) {\n                const contentChildren = children(content);\n                if (contentChildren.length !== 1 || !isEmptyBlock(contentChildren[0])) {\n                    toggle.after(...children(content));\n                }\n                const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n                baseContainer.appendChild(this.document.createElement(\"br\"));\n                toggle.replaceWith(baseContainer);\n                this.dependencies.selection.setCursorStart(baseContainer);\n                return true;\n            }\n            const insertBefore = targetOffset === 0 && blockToSplit.parentElement === title;\n            const [beforeSplit, afterSplit] = this.dependencies.split.splitElementBlock({\n                targetNode,\n                targetOffset,\n                blockToSplit,\n            });\n            if (beforeSplit && afterSplit) {\n                if (content.parentElement.matches(\".d-none\") || insertBefore) {\n                    const newToggle = this.renderToggleBlock();\n                    const newToggleBlock = newToggle.querySelector(toggleSelector);\n                    const newTitleEl = newToggle.querySelector(titleSelector);\n                    const dir = toggle.getAttribute(\"dir\");\n                    if (dir) {\n                        newToggleBlock.setAttribute(\"dir\", dir);\n                    }\n                    if (insertBefore) {\n                        toggle.before(newToggle);\n                        newTitleEl.replaceChildren(beforeSplit);\n                    } else {\n                        toggle.after(newToggle);\n                        newTitleEl.replaceChildren(afterSplit);\n                    }\n                } else {\n                    const firstChild = content.firstElementChild;\n                    if (isEmptyBlock(firstChild)) {\n                        firstChild.replaceWith(afterSplit);\n                    } else {\n                        content.prepend(afterSplit);\n                    }\n                }\n                this.dependencies.selection.setCursorStart(afterSplit);\n            }\n            return true;\n        }\n    }\n\n    /**\n     * Handles the tab behavior. This means that when we are inside a toggle title and we have a toggle\n     * as previous sibling of the embedded component, the current toggle is indented inside the content of\n     * the previous one.\n     */\n    handleTab() {\n        const toggle = this.getToggleFromTitleSelection();\n        if (toggle) {\n            const previousSibling = toggle.previousSibling;\n            if (previousSibling?.matches?.(toggleSelector)) {\n                const cursors = this.dependencies.selection.preserveSelection();\n                const previousSiblingContent = previousSibling.querySelector(contentSelector);\n                if (\n                    children(previousSiblingContent).length === 1 &&\n                    isEmptyBlock(previousSiblingContent.firstElementChild)\n                ) {\n                    previousSiblingContent.replaceChildren(toggle);\n                } else {\n                    previousSiblingContent.append(toggle);\n                }\n                const content = toggle.querySelector(contentSelector);\n                if (!content.parentElement.matches(\".d-none\")) {\n                    toggle.after(...children(content));\n                }\n                this.forceToggle(previousSibling, {\n                    showContent: true,\n                    restoreSelection: cursors.restore,\n                });\n                this.dependencies.history.addStep();\n            }\n            return true;\n        }\n    }\n\n    insertToggleBlock() {\n        const block = this.renderToggleBlock();\n        const target = block.querySelector(`${titleSelector} > ${baseContainerGlobalSelector}`);\n        this.dependencies.dom.insert(block);\n        this.dependencies.selection.setCursorStart(target);\n        this.dependencies.history.addStep();\n    }\n\n    manageToggleFromTitle() {\n        const toggle = this.getToggleFromTitleSelection();\n        if (!toggle) {\n            return;\n        }\n        this.forceToggle(toggle);\n    }\n\n    normalize(element) {\n        const cursors = this.dependencies.selection.preserveSelection();\n        let shouldRestoreCursor = false;\n        for (const titleChild of selectElements(\n            element,\n            `${toggleSelector} ${titleSelector} > *:first-child`\n        )) {\n            const title = titleChild.parentElement;\n            const toggle = closestElement(title, toggleSelector);\n            if (titleChild.nextElementSibling) {\n                const nodes = children(titleChild.parentElement);\n                title.replaceChildren(nodes.shift());\n                toggle.after(...nodes);\n                shouldRestoreCursor = true;\n            }\n            if (!isParagraphRelatedElement(titleChild)) {\n                toggle.after(titleChild);\n                shouldRestoreCursor = true;\n            }\n        }\n        if (shouldRestoreCursor) {\n            cursors.restore();\n        }\n        for (const emptyToggleNode of selectElements(\n            element,\n            `${toggleSelector} [data-embedded-editable]:empty`\n        )) {\n            const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n            baseContainer.appendChild(this.document.createElement(\"br\"));\n            emptyToggleNode.replaceChildren(baseContainer);\n        }\n    }\n\n    renderToggleBlock() {\n        const baseContainer = this.dependencies.baseContainer.createBaseContainer();\n        return parseHTML(\n            this.document,\n            renderToString(\"html_editor.EmbeddedToggleBlockBlueprint\", {\n                baseContainerNodeName: baseContainer.nodeName,\n                baseContainerAttributes: {\n                    class: baseContainer.className,\n                },\n                embeddedProps: JSON.stringify({ toggleBlockId: this.getUniqueIdentifier() }),\n            })\n        );\n    }\n\n    showPowerButtons(selection) {\n        return (\n            selection.isCollapsed &&\n            !closestElement(selection.anchorNode, `${toggleSelector} ${titleSelector}`)\n        );\n    }\n}\n", "import { VideoPlugin } from \"@html_editor/main/media/video_plugin\";\nimport { EmbeddedVideoSelector } from \"./video_selector_dialog/embedded_video_selector\";\n\n/**\n * This plugin is meant to replace the Video plugin.\n */\nexport class EmbeddedVideoPlugin extends VideoPlugin {\n    static id = \"embeddedVideo\";\n    static dependencies = [\"embeddedComponents\", \"selection\", \"history\", \"overlay\", \"media\"];\n\n    // Extends the base class resources\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        ...this.resources,\n        mount_component_handlers: this.extendEmbeddedVideoProps.bind(this),\n    };\n\n    /** @override */\n    get componentForMediaDialog() {\n        return EmbeddedVideoSelector;\n    }\n\n    /**\n     * @param {Object} props\n     */\n    extendEmbeddedVideoProps({ name, props }) {\n        if (name === \"video\") {\n            Object.assign(props, {\n                createOverlay: (Component, props = {}, options) =>\n                    this.dependencies.overlay.createOverlay(Component, props, options),\n                focusEditable: () => this.dependencies.selection.focusEditable(),\n                addStep: () => this.dependencies.history.addStep(),\n                openVideoSelectorDialog: (save, media) => {\n                    this.openVideoSelectorDialog(save, media);\n                },\n            });\n        }\n    }\n\n    /**\n     * Open media dialog allowing the user to insert a video\n     * @param {function} save\n     * @param {HTMLIFrameElement} iframe\n     */\n    openVideoSelectorDialog(save, iframe) {\n        this.dependencies.media.openMediaDialog({\n            node: iframe,\n            save: (elements, [media]) => {\n                if (media.src) {\n                    save(media);\n                }\n            },\n            visibleTabs: [\"VIDEOS\"],\n        });\n    }\n}\n", "import { YoutubePlugin } from \"@html_editor/main/youtube_plugin\";\nimport { EmbeddedVideoSelector } from \"./video_selector_dialog/embedded_video_selector\";\n\nexport class EmbeddedYoutubePlugin extends YoutubePlugin {\n    static id = \"embeddedYoutube\";\n    static dependencies = [...super.dependencies, \"embeddedComponents\"];\n\n    /** @override */\n    createVideoElement(videoData) {\n        const { video_id: videoId, platform, params } = videoData;\n        return EmbeddedVideoSelector.createElements([{ videoId, platform, params }])[0];\n    }\n}\n", "import { VideoSelector } from \"@html_editor/main/media/media_dialog/video_selector\";\nimport { renderToElement } from \"@web/core/utils/render\";\n\nexport class EmbeddedVideoSelector extends VideoSelector {\n    /** @override */\n    static createElements(selectedMedia) {\n        return selectedMedia.map((media) =>\n            renderToElement(\"html_editor.EmbeddedVideoBlueprint\", {\n                embeddedProps: JSON.stringify({\n                    videoId: media.videoId,\n                    platform: media.platform,\n                    params: media.params || {},\n                }),\n            })\n        );\n    }\n}\n", "import { nodeToTree } from \"@html_editor/core/history_plugin\";\nimport { Plugin } from \"@html_editor/plugin\";\nimport { withSequence } from \"@html_editor/utils/resource\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { renderToElement } from \"@web/core/utils/render\";\n\n/**\n * @typedef { Object } EmbeddedComponentShared\n * @property { EmbeddedComponentPlugin['renderBlueprintToElement'] } renderBlueprintToElement\n */\n\n/**\n * @typedef {((arg: { name, env, props }) => void)[]} mount_component_handlers\n * @typedef {(() => void)[]} post_mount_component_handlers\n */\n\n/**\n * This plugin is responsible with providing the API to manipulate/insert\n * sub components in an editor.\n */\nexport class EmbeddedComponentPlugin extends Plugin {\n    static id = \"embeddedComponents\";\n    static dependencies = [\"history\", \"protectedNode\", \"selection\"];\n    static shared = [\"renderBlueprintToElement\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        normalize_handlers: withSequence(0, this.normalize.bind(this)),\n        clean_for_save_handlers: ({ root }) => this.cleanForSave(root),\n        attribute_change_handlers: this.onChangeAttribute.bind(this),\n        restore_savepoint_handlers: () => this.handleComponents(this.editable),\n        history_reset_handlers: () => this.handleComponents(this.editable),\n        history_reset_from_steps_handlers: () => this.handleComponents(this.editable),\n        step_added_handlers: ({ stepCommonAncestor }) => this.handleComponents(stepCommonAncestor),\n        external_step_added_handlers: () => this.handleComponents(this.editable),\n\n        serializable_descendants_processors: this.processDescendantsToSerialize.bind(this),\n        attribute_change_processors: this.onChangeAttribute.bind(this),\n        savable_mutation_record_predicates: this.isMutationRecordSavable.bind(this),\n        move_node_whitelist_selectors: \"[data-embedded]\",\n    };\n\n    setup() {\n        this.components = new Set();\n        // map from node to component info\n        this.nodeMap = new WeakMap();\n        this.app = this.config.embeddedComponentInfo.app;\n        this.env = this.config.embeddedComponentInfo.env ?? {};\n        this.hostToStateChangeManagerMap = new WeakMap();\n        this.hostToOnComponentInsertedMap = new WeakMap();\n        this.embeddedComponents = memoize((embeddedComponents = []) => {\n            const result = {};\n            for (const embedding of embeddedComponents) {\n                // TODO ABD: Any embedding with the same name as another will overwrite it.\n                // File currently relies on this system. Change it ?\n                result[embedding.name] = embedding;\n            }\n            return result;\n        });\n        // First mount is done during history_reset_handlers which happens\n        // when start_edition_handlers are called.\n    }\n\n    isMutationRecordSavable(record) {\n        const info = this.nodeMap.get(record.target);\n        if (\n            info &&\n            record.type === \"attributes\" &&\n            record.attributeName === \"data-embedded-props\"\n        ) {\n            // This attribute is determined independently for each user\n            // through `data-embedded-state` attribute mutations.\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * @typedef {import(\"@html_editor/core/history_plugin\").Tree} Tree\n     *\n     * @param {Node} elem\n     * @param {Tree[]} serializableDescendants\n     * @returns {Tree[]}\n     */\n    processDescendantsToSerialize(elem, serializableDescendants) {\n        const embedding = this.getEmbedding(elem);\n        if (!embedding) {\n            return serializableDescendants;\n        }\n        return Object.values(embedding.getEditableDescendants?.(elem) || {}).map(nodeToTree);\n    }\n\n    handleComponents(elem) {\n        this.destroyRemovedComponents([...this.components]);\n        this.forEachEmbeddedComponentHost(elem, (host, embedding) => {\n            const info = this.nodeMap.get(host);\n            if (!info) {\n                this.mountComponent(host, embedding);\n            }\n        });\n    }\n\n    forEachEmbeddedComponentHost(elem, callback) {\n        const selector = `[data-embedded]`;\n        const targets = [...elem.querySelectorAll(selector)];\n        if (elem.matches(selector)) {\n            targets.unshift(elem);\n        }\n        for (const host of targets) {\n            const embedding = this.getEmbedding(host);\n            if (!embedding) {\n                continue;\n            }\n            callback(host, embedding);\n        }\n    }\n\n    getEmbedding(host) {\n        return this.embeddedComponents(this.getResource(\"embedded_components\"))[\n            host.dataset.embedded\n        ];\n    }\n\n    /**\n     * Apply an embedded state change received from `data-embedded-state`\n     * attribute. In some cases (undo/redo/revertStepsUntil history operations),\n     * the attribute has to be set to a new value, computed by the\n     * stateChangeManager.\n     *\n     * @param {Object} attributeChange @see HistoryPlugin\n     * @param { Object } options\n     * @param { boolean } options.forNewStep whether the mutation is being used\n     *        to create a new step\n     * @returns {string} new attribute value to set on the node, which might be\n     *        unchanged\n     */\n    onChangeAttribute(attributeChange, { forNewStep = false } = {}) {\n        const attributeValue = attributeChange.value;\n        let newAttributeValue;\n        if (attributeChange.attributeName === \"data-embedded-state\") {\n            const attrState = attributeChange.reverse\n                ? attributeChange.oldValue\n                : attributeChange.value;\n            const stateChangeManager = this.getStateChangeManager(attributeChange.target);\n            if (stateChangeManager) {\n                // onStateChanged returns undefined if no change is needed for\n                // the attribute value\n                newAttributeValue = stateChangeManager.onStateChanged(attrState, {\n                    reverse: attributeChange.reverse,\n                    forNewStep,\n                });\n            }\n        }\n        return newAttributeValue || attributeValue;\n    }\n\n    getStateChangeManager(host) {\n        const embedding = this.getEmbedding(host);\n        if (!(\"getStateChangeManager\" in embedding)) {\n            return null;\n        }\n        if (!this.hostToStateChangeManagerMap.has(host)) {\n            const config = {\n                host,\n                commitStateChanges: () => this.dependencies.history.addStep(),\n            };\n            const stateChangeManager = embedding.getStateChangeManager(config);\n            stateChangeManager.setup();\n            this.hostToStateChangeManagerMap.set(host, stateChangeManager);\n        }\n        return this.hostToStateChangeManagerMap.get(host);\n    }\n\n    mountComponent(\n        host,\n        { Component, getEditableDescendants, getProps, name, getStateChangeManager }\n    ) {\n        const props = getProps?.(host) || {};\n        const env = Object.create(this.env);\n        env.editorShared = {};\n        if (getStateChangeManager) {\n            env.getStateChangeManager = this.getStateChangeManager.bind(this);\n        }\n        if (getEditableDescendants) {\n            env.getEditableDescendants = getEditableDescendants;\n            // Enable the automatic selection restoration feature in @see useEditableDescendants\n            Object.assign(env.editorShared, {\n                selection: { ...this.dependencies.selection },\n            });\n        }\n        this.dispatchTo(\"mount_component_handlers\", { name, env, props });\n        const root = this.app.createRoot(Component, {\n            props,\n            env,\n        });\n        root.mount(host);\n        // Patch mount fiber to hook into the exact call stack where root is\n        // mounted (but before). This will remove host children synchronously\n        // just before adding the root rendered html.\n        const fiber = root.node.fiber;\n        const fiberComplete = fiber.complete;\n        fiber.complete = () => {\n            host.replaceChildren();\n            fiberComplete.call(fiber);\n            this.dispatchTo(\"post_mount_component_handlers\");\n        };\n        const onComponentInserted = this.extractOnComponentInserted(host);\n        if (onComponentInserted) {\n            // If a pending operation should be executed after the first mount\n            // of an inserted blueprint, add it as the last `onMounted` callback\n            root.node.mounted.push(onComponentInserted);\n        }\n        const info = {\n            root,\n            host,\n        };\n        this.components.add(info);\n        this.nodeMap.set(host, info);\n    }\n\n    destroyRemovedComponents(infos) {\n        // Avoid registering mutations if removed hosts are handled in\n        // the same microtask as when they were removed.\n        this.dependencies.history.ignoreDOMMutations(() => {\n            for (const info of infos) {\n                if (!this.editable.contains(info.host)) {\n                    const host = info.host;\n                    const display = host.style.display;\n                    const parentNode = host.parentNode;\n                    const clone = host.cloneNode(false);\n                    if (parentNode) {\n                        parentNode.replaceChild(clone, host);\n                    }\n                    host.style.display = \"none\";\n                    this.editable.after(host);\n                    this.destroyComponent(info);\n                    if (parentNode) {\n                        parentNode.replaceChild(host, clone);\n                    } else {\n                        host.remove();\n                    }\n                    host.style.display = display;\n                    if (!host.getAttribute(\"style\")) {\n                        host.removeAttribute(\"style\");\n                    }\n                }\n            }\n        });\n    }\n\n    deepDestroyComponent({ host }) {\n        const removed = [];\n        this.forEachEmbeddedComponentHost(host, (containedHost) => {\n            const info = this.nodeMap.get(containedHost);\n            if (info) {\n                if (this.editable.contains(containedHost)) {\n                    this.destroyComponent(info);\n                } else {\n                    removed.push(info);\n                }\n            }\n        });\n        this.destroyRemovedComponents(removed);\n    }\n\n    /**\n     * Should not be called directly as it will not handle recursivity and\n     * removed components @see deepDestroyComponent\n     */\n    destroyComponent({ root, host }) {\n        const { getEditableDescendants } = this.getEmbedding(host);\n        const editableDescendants = getEditableDescendants?.(host) || {};\n        root.destroy();\n        this.components.delete(arguments[0]);\n        this.nodeMap.delete(host);\n        host.append(...Object.values(editableDescendants));\n    }\n\n    destroy() {\n        super.destroy();\n        for (const info of [...this.components]) {\n            if (this.components.has(info)) {\n                this.deepDestroyComponent(info);\n            }\n        }\n    }\n\n    /**\n     * @param {String} template blueprint for the embedded Component\n     * @param {Object} [context] rendering context\n     * @param {Function} [onComponentInserted] function to be executed when\n     *        it is first mounted after it was inserted in the DOM. It will not\n     *        be executed if the blueprint is removed from the DOM before the\n     *        first mount nor if the component is mounted again afterwards.\n     * @returns {HTMLElement} host\n     */\n    renderBlueprintToElement(template, context = {}, onComponentInserted = undefined) {\n        const host = renderToElement(template, context);\n        if (onComponentInserted) {\n            this.hostToOnComponentInsertedMap.set(host, onComponentInserted);\n        }\n        return host;\n    }\n\n    extractOnComponentInserted(host) {\n        const onComponentInserted = this.hostToOnComponentInsertedMap.get(host);\n        this.hostToOnComponentInsertedMap.delete(host);\n        return onComponentInserted;\n    }\n\n    normalize(elem) {\n        this.forEachEmbeddedComponentHost(elem, (host, { getEditableDescendants }) => {\n            this.dependencies.protectedNode.setProtectingNode(host, true);\n            const editableDescendants = getEditableDescendants?.(host) || {};\n            for (const editableDescendant of Object.values(editableDescendants)) {\n                this.dependencies.protectedNode.setProtectingNode(editableDescendant, false);\n            }\n        });\n    }\n\n    cleanForSave(clone) {\n        this.forEachEmbeddedComponentHost(clone, (host, { getEditableDescendants }) => {\n            // In this case, host is a cloned element, there is no OWL root\n            // attached to it.\n            const editableDescendants = getEditableDescendants?.(host) || {};\n            host.replaceChildren();\n            for (const editableDescendant of Object.values(editableDescendants)) {\n                delete editableDescendant.dataset.oeProtected;\n                host.append(editableDescendant);\n            }\n            delete host.dataset.oeProtected;\n            delete host.dataset.embeddedState;\n        });\n    }\n}\n", "import { Component, useState } from \"@odoo/owl\";\n\nexport class QWebPicker extends Component {\n    static template = \"html_editor.QWebPicker\";\n    static props = [\"groups\", \"select\"];\n\n    setup() {\n        this.state = useState({ groups: this.props.groups });\n    }\n\n    onChange(ev) {\n        const [groupIndex, elementIndex] = ev.target.value.split(\",\");\n        this.props.select(this.state.groups[groupIndex][elementIndex].node);\n    }\n}\n", "import { Plugin } from \"@html_editor/plugin\";\nimport { closestElement, selectElements } from \"@html_editor/utils/dom_traversal\";\nimport { leftPos, rightPos } from \"@html_editor/utils/position\";\nimport { QWebPicker } from \"./qweb_picker\";\nimport { isElement } from \"@html_editor/utils/dom_info\";\nimport { withSequence } from \"@html_editor/utils/resource\";\n\nconst isUnsplittableQWebElement = (node) =>\n    isElement(node) &&\n    (node.tagName === \"T\" ||\n        [\n            \"t-field\",\n            \"t-if\",\n            \"t-elif\",\n            \"t-else\",\n            \"t-foreach\",\n            \"t-value\",\n            \"t-esc\",\n            \"t-out\",\n            \"t-raw\",\n        ].some((attr) => node.getAttribute(attr)));\n\nconst PROTECTED_QWEB_SELECTOR = \"[t-esc], [t-raw], [t-out], [t-field]\";\nconst QWEB_DATA_ATTRIBUTES = [\n    \"data-oe-t-group\",\n    \"data-oe-t-inline\",\n    \"data-oe-t-selectable\",\n    \"data-oe-t-group-active\",\n];\nconst dataAttributesSelector = QWEB_DATA_ATTRIBUTES.map((attr) => `[${attr}]`).join(\", \");\n\nexport const isUnremovableQWebElement = (node) =>\n    node.getAttribute?.(\"t-set\") || node.getAttribute?.(\"t-call\");\n\nexport class QWebPlugin extends Plugin {\n    static id = \"qweb\";\n    static dependencies = [\"overlay\", \"protectedNode\", \"selection\"];\n    /** @type {import(\"plugins\").EditorResources} */\n    resources = {\n        /** Handlers */\n        selectionchange_handlers: withSequence(8, this.onSelectionChange.bind(this)),\n        clean_for_save_handlers: ({ root }) => {\n            this.clearDataAttributes(root);\n            for (const element of root.querySelectorAll(PROTECTED_QWEB_SELECTOR)) {\n                element.removeAttribute(\"contenteditable\");\n                delete element.dataset.oeProtected;\n            }\n        },\n        normalize_handlers: withSequence(0, this.normalize.bind(this)),\n\n        system_attributes: QWEB_DATA_ATTRIBUTES,\n        unremovable_node_predicates: isUnremovableQWebElement,\n        unsplittable_node_predicates: isUnsplittableQWebElement,\n        clipboard_content_processors: this.clearDataAttributes.bind(this),\n        legit_empty_link_predicates: (linkEl) =>\n            linkEl.getAttributeNames().some((name) => name.startsWith(\"t-\")),\n    };\n\n    setup() {\n        this.editable.classList.add(\"odoo-editor-qweb\");\n        this.picker = this.dependencies.overlay.createOverlay(QWebPicker, {\n            positionOptions: { position: \"top-start\" },\n        });\n        this.addDomListener(this.editable, \"click\", this.onClick);\n        this.groupIndex = 0;\n    }\n\n    isValidTargetForDomListener(ev) {\n        if (\n            ev.type === \"click\" &&\n            ev.target &&\n            closestElement(ev.target, PROTECTED_QWEB_SELECTOR)\n        ) {\n            // Allow clicking on a protected QWEB node to open the custom toolbar.\n            return true;\n        }\n        return super.isValidTargetForDomListener(ev);\n    }\n\n    /**\n     * @param { SelectionData } selectionData\n     */\n    onSelectionChange() {\n        if (this.picker.isOpen) {\n            this.picker.close();\n        }\n    }\n\n    normalize(root) {\n        this.normalizeInline(root);\n\n        for (const element of selectElements(root, PROTECTED_QWEB_SELECTOR)) {\n            this.dependencies.protectedNode.setProtectingNode(element, true);\n        }\n        this.applyGroupQwebBranching(root);\n    }\n\n    checkAllInline(el) {\n        return [...el.children].every((child) => {\n            if (child.tagName === \"T\") {\n                return this.checkAllInline(child);\n            } else {\n                return (\n                    child.nodeType !== Node.ELEMENT_NODE ||\n                    this.window.getComputedStyle(child).display === \"inline\"\n                );\n            }\n        });\n    }\n\n    normalizeInline(root) {\n        for (const el of selectElements(root, \"t\")) {\n            if (this.checkAllInline(el)) {\n                el.setAttribute(\"data-oe-t-inline\", \"true\");\n            }\n        }\n    }\n\n    getNodeGroups(node) {\n        const branchNode = node.closest(\"[data-oe-t-group]\");\n        if (!branchNode) {\n            return [];\n        }\n        const groupId = branchNode.getAttribute(\"data-oe-t-group\");\n        const group = [];\n        for (const node of branchNode.parentElement.querySelectorAll(\n            `[data-oe-t-group='${groupId}']`\n        )) {\n            let label = \"\";\n            if (node.hasAttribute(\"t-if\")) {\n                label = `if: ${node.getAttribute(\"t-if\")}`;\n            } else if (node.hasAttribute(\"t-elif\")) {\n                label = `elif: ${node.getAttribute(\"t-elif\")}`;\n            } else if (node.hasAttribute(\"t-else\")) {\n                label = \"else\";\n            }\n            group.push({\n                groupId,\n                node,\n                label,\n                isActive: node.getAttribute(\"data-oe-t-group-active\") === \"true\",\n            });\n        }\n        return this.getNodeGroups(branchNode.parentElement).concat([group]);\n    }\n\n    onClick(ev) {\n        if (this.picker.isOpen) {\n            this.picker.close();\n        }\n        if (ev.detail > 1) {\n            const selectionData = this.dependencies.selection.getSelectionData();\n            const selection = selectionData.documentSelection;\n            const qwebNode =\n                selection &&\n                selection.anchorNode &&\n                closestElement(selection.anchorNode, \"[t-field],[t-esc],[t-out]\");\n            if (qwebNode && this.editable.contains(qwebNode)) {\n                // select the whole qweb node\n                const [anchorNode, anchorOffset] = leftPos(qwebNode);\n                const [focusNode, focusOffset] = rightPos(qwebNode);\n                this.dependencies.selection.setSelection({\n                    anchorNode,\n                    anchorOffset,\n                    focusNode,\n                    focusOffset,\n                });\n            }\n        }\n        const targetNode = ev.target;\n        if (targetNode.closest(\"[data-oe-t-group]\")) {\n            this.selectNode(targetNode);\n        }\n    }\n\n    selectNode(node) {\n        const editableSelection = this.dependencies.selection.getSelectionData().editableSelection;\n        if (!editableSelection.isCollapsed) {\n            return;\n        }\n        this.selectedNode = node;\n        this.picker.open({\n            target: node,\n            props: {\n                groups: this.getNodeGroups(node),\n                select: this.select.bind(this),\n            },\n        });\n    }\n\n    applyGroupQwebBranching(root) {\n        const tNodes = selectElements(root, \"[t-if], [t-elif], [t-else]\");\n        const groupsEncounter = new Set();\n        for (const node of tNodes) {\n            const prevNode = node.previousElementSibling;\n\n            let groupId;\n            if (prevNode && !node.hasAttribute(\"t-if\")) {\n                // Make the first t-if selectable, if prevNode is not a t-if,\n                // it's already data-oe-t-selectable.\n                prevNode.setAttribute(\"data-oe-t-selectable\", \"true\");\n                groupId = parseInt(prevNode.getAttribute(\"data-oe-t-group\"));\n                node.setAttribute(\"data-oe-t-selectable\", \"true\");\n            } else {\n                groupId = this.groupIndex++;\n            }\n            groupsEncounter.add(groupId);\n            node.setAttribute(\"data-oe-t-group\", groupId);\n        }\n        for (const groupId of groupsEncounter) {\n            const isOneElementActive = root.querySelector(\n                `[data-oe-t-group='${groupId}'][data-oe-t-group-active]`\n            );\n            // If there is no element in groupId activated, activate the first\n            // one.\n            if (!isOneElementActive) {\n                const firstElementToActivate = selectElements(\n                    root,\n                    `[data-oe-t-group='${groupId}']`\n                ).next().value;\n                firstElementToActivate.setAttribute(\"data-oe-t-group-active\", \"true\");\n            }\n        }\n    }\n\n    select(node) {\n        const groupId = node.getAttribute(\"data-oe-t-group\");\n        const activeElement = node.parentElement.querySelector(\n            `[data-oe-t-group='${groupId}'][data-oe-t-group-active]`\n        );\n        if (activeElement === node) {\n            return;\n        }\n        activeElement.removeAttribute(\"data-oe-t-group-active\");\n        node.setAttribute(\"data-oe-t-group-active\", \"true\");\n        this.selectedNode = node;\n        this.picker.close();\n        this.selectNode(node);\n    }\n\n    clearDataAttributes(root) {\n        for (const node of root.querySelectorAll(dataAttributesSelector)) {\n            QWEB_DATA_ATTRIBUTES.forEach((attr) => node.removeAttribute(attr));\n        }\n    }\n}\n", "import { registry } from \"@web/core/registry\";\n\nexport const uploadLocalFileService = {\n    dependencies: [\"upload\", \"orm\"],\n    start(env, { upload: uploadService, orm }) {\n        const input = document.createElement(\"input\");\n        input.type = \"file\";\n\n        /**\n         * Open the system file selector and return the selected files.\n         *\n         * @param {Object} [options]\n         * @param {boolean} [options.multiple=true]\n         * @param {string} [options.accept]\n         * @returns {Promise<FileList>}\n         */\n        async function selectLocalFiles({ multiple, accept }) {\n            input.multiple = multiple;\n            input.accept = accept;\n            input.value = \"\"; // clear previously selected files\n\n            // Open system's file selector\n            input.click();\n\n            // Wait for user to select files or cancel.\n            await new Promise((resolve) => {\n                const resolveAndClear = () => {\n                    resolve();\n                    input.removeEventListener(\"change\", resolveAndClear);\n                    input.removeEventListener(\"cancel\", resolveAndClear);\n                };\n                // Detect file(s) selected\n                input.addEventListener(\"change\", resolveAndClear);\n                // Detect file selector closed without selecting files (cancel)\n                input.addEventListener(\"cancel\", resolveAndClear);\n            });\n            return input.files;\n        }\n\n        /**\n         * @param {FileList} files\n         * @param {Object} recordInfo\n         * @returns {Promise<Object[]>} attachments\n         */\n        async function filesToAttachments(files, { resModel, resId }) {\n            const attachments = [];\n            await uploadService.uploadFiles(files, { resModel, resId }, (attachment) => {\n                attachments.push(attachment);\n            });\n            return attachments;\n        }\n\n        /**\n         * Open the system file selector and upload the selected files.\n         *\n         * @param {Object} recordInfo\n         * @param {Object} [options]\n         * @param {string} [options.accept] Accepted file types\n         * @param {boolean} [options.multiple=false] Allow multiple files to be selected\n         * @param {boolean} [options.accessToken=false] Add access token to uploaded files\n         * @returns {Promise<Object[]>} attachments\n         */\n        async function upload(\n            { resId, resModel },\n            { accept = \"*/*\", multiple = false, accessToken = false } = {}\n        ) {\n            try {\n                const files = await selectLocalFiles({ multiple, accept });\n                const attachments = await filesToAttachments(files, { resModel, resId });\n                if (accessToken && attachments.length && !attachments[0].public) {\n                    await addAccessToken(attachments);\n                }\n                return attachments;\n            } catch {\n                // The upload service displays a either a notification or an\n                // error message in the progress toast.\n                return [];\n            }\n        }\n\n        /**\n         * @param {Object[]} attachments\n         * @returns {Promise<Object[]>}\n         */\n        async function addAccessToken(attachments) {\n            const accessTokens = await orm.call(\"ir.attachment\", \"generate_access_token\", [\n                attachments.map((a) => a.id),\n            ]);\n            attachments.forEach((attachment, index) => {\n                attachment.access_token = accessTokens[index];\n            });\n            return attachments;\n        }\n\n        /**\n         * @param {Object} attachments\n         * @param {Object} [options]\n         * @returns {string}\n         */\n        function getURL(attachment, { unique, download, accessToken } = {}) {\n            let url = `/web/content/${attachment.id}`;\n            const queryParams = [];\n            if (unique) {\n                queryParams.push(`unique=${encodeURIComponent(attachment.checksum)}`);\n            }\n            if (download) {\n                queryParams.push(\"download=true\");\n            }\n            if (accessToken && attachment.access_token) {\n                queryParams.push(`access_token=${attachment.access_token}`);\n            }\n            if (queryParams.length) {\n                url += `?${queryParams.join(\"&\")}`;\n            }\n            return url;\n        }\n\n        return { upload, addAccessToken, getURL };\n    },\n};\n\nregistry.category(\"services\").add(\"uploadLocalFiles\", uploadLocalFileService);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport comparisonUtils from '@website_sale_comparison/js/website_sale_comparison_utils';\nimport { redirect } from '@web/core/utils/urls';\n\nexport class ComparisonPage extends Interaction {\n    static selector = '#o_comparelist_table';\n\n    dynamicSelectors = {\n        ...this.dynamicSelectors,\n        _miniSticky: () => document.querySelector('#miniStickyComparison'),\n        _mainScroll: () => document.querySelector('.table-comparator')?.closest('.overflow-x-auto'),\n        _miniScroll: () => document.querySelector('#miniStickyComparison .overflow-x-auto'),\n        _backButton: () => document.querySelector('button[name=\"comparison_back_button\"]'),\n        _clearAllButton: () => document.querySelector('button[name=\"comparison_clear_all_button\"]'),\n    };\n\n    dynamicContent = {\n        'button[name=\"comparison_add_to_cart\"]': { 't-on-click': this.addToCart },\n        '.o_comparelist_remove': { 't-on-click': this.removeProduct },\n        _backButton: { 't-on-click': () => redirect('/shop') },\n        _clearAllButton: { 't-on-click': this.clearAllProducts },\n    };\n\n    // TODO the sticky logic could probably make use of the WebsiteSaleStickyObject\n    // interaction. We'd simply need to remove the offset that comes with the interaction\n    // and handle the fact that the sticky element is hidden and appears when the user scrolls.\n\n    setup() {\n        this.position = 0;\n    }\n\n    start() {\n        this._adaptToHeaderChange();\n        this.registerCleanup(this.services.website_menus.registerCallback(this._adaptToHeaderChange.bind(this)));\n        this._initMiniStickyComparison();\n    }\n\n    /**\n     * Adapt the position of elements when the header changes.\n     *\n     * @private\n     */\n    _adaptToHeaderChange() {\n        let position = 0;\n\n        // Calculate total height of fixed elements at top\n        for (const el of this.el.ownerDocument.querySelectorAll(\".o_top_fixed_element\")) {\n            position += el.offsetHeight;\n        }\n\n        if (this.position !== position) {\n            this.position = position;\n            this.updateContent();\n\n            // Update mini sticky position if it exists\n            const miniStickyEl = this.dynamicSelectors._miniSticky();\n            if (miniStickyEl) {\n                miniStickyEl.style.top = `${position}px`;\n            }\n        }\n    }\n\n    /**\n     * Clear all products from the comparison.\n     */\n    clearAllProducts() {\n        comparisonUtils.clearComparisonProducts(this.bus);\n        redirect('/shop');\n    }\n\n    /**\n     * Initialize the mini sticky comparison overview.\n     *\n     * @private\n     */\n    _initMiniStickyComparison() {\n        const miniStickyEl = this.dynamicSelectors._miniSticky();\n        const productImagesEl = this.el.querySelector('ul:first-of-type');\n\n        if (!miniStickyEl || !productImagesEl) return;\n\n        // Set initial position\n        miniStickyEl.style.top = `${this.position}px`;\n\n        // Get scroll containers\n        const mainScrollEl = this.dynamicSelectors._mainScroll();\n        const miniScrollEl = this.dynamicSelectors._miniScroll();\n\n        // Handle vertical scroll (show/hide mini sticky)\n        const handleVerticalScroll = () => {\n            const rect = productImagesEl.getBoundingClientRect();\n            const shouldShow = rect.bottom < this.position + 20;\n\n            miniStickyEl.classList.toggle('show', shouldShow);\n            miniStickyEl.classList.toggle('d-none', !shouldShow);\n\n            // Sync horizontal position when showing\n            if (shouldShow && mainScrollEl && miniScrollEl) {\n                miniScrollEl.scrollLeft = mainScrollEl.scrollLeft;\n            }\n        };\n\n        // Handle horizontal scroll sync\n        const syncScroll = (source, target) => {\n            if (!source._syncing) {\n                target._syncing = true;\n                target.scrollLeft = source.scrollLeft;\n                requestAnimationFrame(() => target._syncing = false);\n            }\n        };\n\n        // Bind events\n        window.addEventListener('scroll', handleVerticalScroll, { passive: true });\n        if (mainScrollEl && miniScrollEl) {\n            mainScrollEl.addEventListener('scroll', () => syncScroll(mainScrollEl, miniScrollEl), { passive: true });\n            miniScrollEl.addEventListener('scroll', () => syncScroll(miniScrollEl, mainScrollEl), { passive: true });\n        }\n\n        // Cleanup\n        this.registerCleanup(() => {\n            window.removeEventListener('scroll', handleVerticalScroll);\n        });\n\n        // Initial check\n        handleVerticalScroll();\n    }\n\n    /**\n     * Add a product to the cart from the comparison page.\n     *\n     * @param {Event} ev\n     */\n    addToCart(ev) {\n        const button = ev.currentTarget;\n        const productId = parseInt(button.dataset.productProductId);\n        const productTemplateId = parseInt(button.dataset.productTemplateId);\n        const showQuantity = Boolean(button.dataset.showQuantity);\n\n        this.services['cart'].add({\n            productTemplateId: productTemplateId,\n            productId: productId,\n        }, {\n            showQuantity: showQuantity,\n        });\n    }\n\n    /**\n     * Remove a product from the comparison.\n     *\n     * @param {Event} ev\n     */\n    removeProduct(ev) {\n        const productId = parseInt(ev.currentTarget.dataset.productProductId);\n        comparisonUtils.removeComparisonProduct(productId, null); // No bus needed on comparison page\n\n        const productIds = comparisonUtils.getComparisonProductIds();\n        if (productIds.length === 0) {\n            redirect('/shop');\n        } else {\n            const comparisonUrl = `/shop/compare?products=${encodeURIComponent(productIds.join(','))}`;\n            redirect(comparisonUrl);\n        }\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale_comparison.comparison_page', ComparisonPage);\n", "import { EventBus } from '@odoo/owl';\nimport { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport { redirect } from '@web/core/utils/urls';\nimport wSaleUtils from '@website_sale/js/website_sale_utils';\nimport comparisonUtils from '@website_sale_comparison/js/website_sale_comparison_utils';\nimport {\n    ProductComparisonBottomBar\n} from '@website_sale_comparison/js/product_comparison_bottom_bar/product_comparison_bottom_bar';\n\nexport class ProductComparison extends Interaction {\n    static selector = '.js_sale:not(.o_wsale_comparison_page)';\n\n    dynamicContent = {\n        '.o_add_compare, .o_add_compare_dyn': { 't-on-click': this.addProduct },\n        'input.product_id': { 't-on-change': this.onChangeVariant },\n        '.o_comparelist_remove': { 't-on-click': this.removeProduct },\n    };\n\n    setup() {\n        this.bus = new EventBus();\n        // Mount the ProductComparisonBottomBar on pages with comparison functionality\n        this.mountComponent(\n            this.el,\n            ProductComparisonBottomBar,\n            {\n                bus: this.bus,\n            },\n        );\n    }\n\n    /**\n     * Add a product to the comparison.\n     *\n     * @param {Event} ev\n     */\n    async addProduct(ev) {\n        if (this._checkMaxComparisonProducts()) return;\n\n        const el = ev.currentTarget;\n        let productId = parseInt(el.dataset.productProductId);\n        const form = wSaleUtils.getClosestProductForm(el);\n        if (!productId) {\n            productId = await this.waitFor(rpc('/sale/create_product_variant', {\n                product_template_id: parseInt(el.dataset.productTemplateId),\n                product_template_attribute_value_ids: wSaleUtils.getSelectedAttributeValues(form),\n            }));\n        }\n        if (!productId || this._checkProductAlreadyInComparison(productId)) {\n            comparisonUtils.updateDisabled(el, true);\n            return;\n        }\n\n        comparisonUtils.addComparisonProduct(productId, this.bus);\n        comparisonUtils.updateDisabled(el, true);\n    }\n\n    /**\n     * Enable/disable the \"add to comparison\" button based on the selected variant.\n     *\n     * @param {Event} ev\n     */\n    onChangeVariant(ev) {\n        const input = ev.target;\n        const productId = input.value;\n        const button = input.closest('.js_product')?.querySelector('[data-action=\"o_comparelist\"]');\n        if (button) {\n            const isDisabled = comparisonUtils.getComparisonProductIds().includes(\n                parseInt(productId)\n            );\n            comparisonUtils.updateDisabled(button, isDisabled);\n            button.dataset.productProductId = productId;\n        }\n    }\n\n    /**\n     * Remove a product from the comparison.\n     *\n     * @param {Event} ev\n     */\n    removeProduct(ev) {\n        const productId = parseInt(ev.currentTarget.dataset.productProductId);\n        comparisonUtils.removeComparisonProduct(productId, this.bus);\n\n        const productIds = comparisonUtils.getComparisonProductIds();\n        const comparisonUrl = `/shop/compare?products=${encodeURIComponent(productIds.join(','))}`;\n        redirect(productIds.length ? comparisonUrl : '/shop');\n    }\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n\n\n    /**\n     * Check whether the maximum number of products in the comparison has been reached, and if so,\n     * show a warning.\n     *\n     * @return {boolean} Whether the maximum number of products in the comparison has been reached.\n     */\n    _checkMaxComparisonProducts() {\n        if (\n            comparisonUtils.getComparisonProductIds().length\n            >= comparisonUtils.MAX_COMPARISON_PRODUCTS\n        ) {\n            this.services.notification.add(\n                _t(\"You can compare up to 4 products at a time.\"),\n                {\n                    type: 'warning',\n                    sticky: false,\n                    title: _t(\"Too many products to compare\"),\n                },\n            );\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Check whether the product is already in the comparison, and if so, show a warning.\n     *\n     * @param productId The ID of the product to check.\n     * @return {boolean} Whether the product is already in the comparison.\n     */\n    _checkProductAlreadyInComparison(productId) {\n        if (comparisonUtils.getComparisonProductIds().includes(productId)) {\n            this.services.notification.add(\n                _t(\"This product has already been added to the comparison.\"),\n                { type: 'warning', sticky: false },\n            );\n            return true;\n        }\n        return false;\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale_comparison.product_comparison', ProductComparison);\n", "import { Component, onWillStart, useState, useSubEnv } from '@odoo/owl';\nimport { rpc } from '@web/core/network/rpc';\nimport { useBus } from '@web/core/utils/hooks';\nimport comparisonUtils from '@website_sale_comparison/js/website_sale_comparison_utils';\nimport { ProductRow } from '../product_row/product_row';\n\nexport class ProductComparisonBottomBar extends Component {\n    static template = 'website_sale_comparison.ProductComparisonBottomBar';\n    static components = { ProductRow };\n    static props = {\n        bus: Object,\n    };\n\n    setup() {\n        super.setup();\n        this.state = useState({ products: new Map() });\n        useBus(this.props.bus, comparisonUtils.COMPARISON_EVENT, (_) => this._loadProducts());\n        useSubEnv({bus: this.props.bus});\n        onWillStart(this._loadProducts);\n    }\n\n    /**\n     * Load the products to compare from the server.\n     *\n     * This method also removes any products that are no longer available from the comparison.\n     */\n    async _loadProducts() {\n        const productIds = comparisonUtils.getComparisonProductIds();\n        if (!productIds.length) {\n            this.state.products.clear();\n            return;\n        }\n        const productData = await rpc('/shop/compare/get_product_data', {\n            product_ids: productIds,\n        });\n\n        this.state.products.clear();\n        productData.forEach((product) => this.state.products.set(product.id, product));\n    }\n\n    /**\n     * Get the URL of the comparison page with the selected products.\n     *\n     * @return {string} The URL of the comparison page.\n     */\n    get comparisonUrl() {\n        const productIds = Array.from(this.state.products.keys());\n        return `/shop/compare?products=${encodeURIComponent(productIds.join(','))}`;\n    }\n\n    /**\n     * Get the count of products being compared.\n     * @return {number} The number of products.\n     */\n    get productCount() {\n        return this.state.products.size;\n    }\n\n\n\n    /**\n     * Clear all products from comparison.\n     */\n    clearAllProducts() {\n        comparisonUtils.clearComparisonProducts(this.env.bus);\n    }\n}\n", "import { Component } from '@odoo/owl';\nimport { formatCurrency } from '@web/core/currency';\nimport comparisonUtils from '@website_sale_comparison/js/website_sale_comparison_utils';\n\nexport class ProductRow extends Component {\n    static template = 'website_sale_comparison.ProductRow';\n    static props = {\n        id: Number,\n        display_name: String,\n        website_url: String,\n        image_url: String,\n        price: Number,\n        strikethrough_price: { type: Number, optional: true },\n        prevent_zero_price_sale: Boolean,\n        currency_id: Number,\n    };\n\n    /**\n     * Remove the product from the comparison.\n     */\n    removeProduct() {\n        comparisonUtils.removeComparisonProduct(this.props.id, this.env.bus);\n        comparisonUtils.enableDisabledProducts([this.props.id], false);\n    }\n\n    /**\n     * Get the price, formatted using the provided currency.\n     *\n     * @return {string} The formatted price.\n     */\n    get formattedPrice() {\n        return formatCurrency(this.props.price, this.props.currency_id);\n    }\n\n    /**\n     * Get the strikethrough price, formatted using the provided currency.\n     *\n     * @return {string} The formatted strikethrough price.\n     */\n    get formattedStrikethroughPrice() {\n        return formatCurrency(this.props.strikethrough_price, this.props.currency_id);\n    }\n}\n", "import { cookie } from '@web/core/browser/cookie';\n\nconst COMPARISON_PRODUCT_IDS_COOKIE_NAME = 'comparison_product_ids';\nconst MAX_COMPARISON_PRODUCTS = 4;\nconst COMPARISON_EVENT = 'comparison_products_changed'\n\n/**\n * Get the IDs of the products to compare from the cookie.\n *\n * @return {Array<number>} The IDs of the products to compare.\n */\nfunction getComparisonProductIds() {\n    return JSON.parse(cookie.get(COMPARISON_PRODUCT_IDS_COOKIE_NAME) || '[]');\n}\n\n/**\n * Set the IDs of the products to compare in the cookie.\n *\n * @param {ArrayLike<number>} productIds The IDs of the products to compare.\n * @param {EventBus} bus\n */\nfunction setComparisonProductIds(productIds, bus) {\n    cookie.set(COMPARISON_PRODUCT_IDS_COOKIE_NAME, JSON.stringify(Array.from(productIds)));\n    notifyComparisonListeners(bus);\n}\n\n/**\n * Add the specified product to the comparison.\n *\n * @param {number} productId\n * @param {EventBus} bus\n */\nfunction addComparisonProduct(productId, bus) {\n    const productIds = new Set(getComparisonProductIds());\n    productIds.add(productId);\n    setComparisonProductIds(productIds, bus);\n}\n\n/**\n * Remove the specified product from the comparison.\n *\n * @param {number} productId\n * @param {EventBus} bus\n */\nfunction removeComparisonProduct(productId, bus) {\n    const productIds = new Set(getComparisonProductIds());\n    productIds.delete(productId);\n    setComparisonProductIds(productIds, bus);\n}\n\n/**\n * Clear all products in comparison list\n *\n * @param {EventBus} bus\n */\nfunction clearComparisonProducts(bus) {\n    const productIds = getComparisonProductIds();\n    cookie.delete(COMPARISON_PRODUCT_IDS_COOKIE_NAME);\n    notifyComparisonListeners(bus);\n    enableDisabledProducts(productIds);\n}\n\n/**\n * Notify comparison listeners using an event bus that the values of productshave changed\n *\n * @param {EventBus} bus\n */\nfunction notifyComparisonListeners(bus) {\n    if (bus) {\n        bus.dispatchEvent(new CustomEvent(COMPARISON_EVENT, { bubbles: true }));\n    }\n}\n\n/**\n * Update the disabled/enabled state of an element.\n *\n * @param {Element} el The element to disable/enable.\n * @param {boolean} isDisabled Whether the element should be disabled.\n */\nfunction updateDisabled(el, isDisabled) {\n    el.disabled = isDisabled;\n    el.classList.toggle('disabled', isDisabled);\n}\n\n/**\n * After removing products from comparison, update the disabled button\n */\nfunction enableDisabledProducts(productIds) {\n    for (const productId of productIds) {\n        const productCompareButton = document.querySelector(\n            `.o_add_compare[data-product-product-id=\"${productId}\"]`\n        );\n        if (productCompareButton) {\n            updateDisabled(productCompareButton, false);\n        }\n    }\n}\n\nexport default {\n    MAX_COMPARISON_PRODUCTS: MAX_COMPARISON_PRODUCTS,\n    COMPARISON_EVENT: COMPARISON_EVENT,\n    getComparisonProductIds: getComparisonProductIds,\n    setComparisonProductIds: setComparisonProductIds,\n    addComparisonProduct: addComparisonProduct,\n    removeComparisonProduct: removeComparisonProduct,\n    clearComparisonProducts: clearComparisonProducts,\n    notifyComparisonListeners: notifyComparisonListeners,\n    updateDisabled: updateDisabled,\n    enableDisabledProducts: enableDisabledProducts,\n};\n", "import { rpc } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { Interaction } from '@web/public/interaction';\nimport wSaleUtils from '@website_sale/js/website_sale_utils';\nimport wishlistUtils from '@website_sale_wishlist/js/website_sale_wishlist_utils';\n\nexport class AddProductToWishlistButton extends Interaction {\n    static selector = '.o_add_wishlist, .o_add_wishlist_dyn';\n    dynamicContent = {\n        _root: { 't-on-click': this.addProduct },\n    };\n\n    /**\n     * Add a product to the wishlist.\n     *\n     * @param {Event} ev\n     */\n    async addProduct(ev) {\n        const el = ev.currentTarget;\n        let productId = parseInt(el.dataset.productProductId);\n        const form = wSaleUtils.getClosestProductForm(el);\n        if (!productId) {\n            productId = await this.waitFor(rpc('/sale/create_product_variant', {\n                product_template_id: parseInt(el.dataset.productTemplateId),\n                product_template_attribute_value_ids: wSaleUtils.getSelectedAttributeValues(form),\n            }));\n        }\n        if (!productId || wishlistUtils.getWishlistProductIds().includes(productId)) return;\n\n        await this.waitFor(rpc('/shop/wishlist/add', { product_id: productId }));\n        wishlistUtils.addWishlistProduct(productId);\n        wishlistUtils.updateWishlistNavBar();\n        wishlistUtils.updateDisabled(el, true);\n        await wSaleUtils.animateClone(\n            $(document.querySelector('.o_wsale_my_wish')),\n            $(document.querySelector('#product_detail_main') ?? el.closest('.o_cart_product') ?? form),\n            25,\n            40,\n        );\n        if (el.classList.contains('o_add_wishlist')) {\n            const iconEl = el.querySelector('.fa');\n            if (iconEl) {\n                iconEl.classList.remove('fa-heart-o');\n                iconEl.classList.add('fa-heart');\n            }\n        }\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale_wishlist.add_product_to_wishlist_button', AddProductToWishlistButton);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport wishlistUtils from '@website_sale_wishlist/js/website_sale_wishlist_utils';\n\nexport class ProductDetail extends Interaction {\n    static selector = '#product_detail';\n    dynamicContent = {\n        'input.product_id': { 't-on-change': this.onChangeVariant },\n    };\n\n    /**\n     * Enable/disable the \"add to wishlist\" button based on the selected variant.\n     *\n     * @param {Event} ev\n     */\n    onChangeVariant(ev) {\n        const input = ev.target;\n        const productId = input.value;\n        const button = input.closest('.js_product')?.querySelector('[data-action=\"o_wishlist\"]');\n        if (button) {\n            const isDisabled = wishlistUtils.getWishlistProductIds().includes(parseInt(productId));\n            wishlistUtils.updateDisabled(button, isDisabled);\n            button.dataset.productProductId = productId;\n        }\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale_wishlist.product_detail', ProductDetail);\n", "import { rpc } from '@web/core/network/rpc';\nimport { registry } from '@web/core/registry';\nimport { redirect } from '@web/core/utils/urls';\nimport { Interaction } from '@web/public/interaction';\nimport wishlistUtils from '@website_sale_wishlist/js/website_sale_wishlist_utils';\n\nexport class ProductWishlist extends Interaction {\n    static selector = '.wishlist-section';\n    dynamicContent = {\n        '.o_wish_rm': { 't-on-click': this.removeProduct },\n        '.o_wish_add': { 't-on-click': this.addToCart },\n    };\n\n    /**\n     * Remove a product from the wishlist.\n     *\n     * @param {Event} ev\n     */\n    async removeProduct(ev) {\n        await this._removeProduct(ev.currentTarget);\n    }\n\n    /**\n     * Add a product to the cart from the wishlist page.\n     *\n     * @param {Event} ev\n     */\n    async addToCart(ev) {\n        const button = ev.currentTarget;\n        const productId = parseInt(button.dataset.productProductId);\n        const productTemplateId = parseInt(button.dataset.productTemplateId);\n        const isCombo = button.dataset.productType === 'combo';\n        const ptavs = JSON.parse(button.dataset.ptavIds || '[]');\n        const showQuantity = Boolean(button.dataset.showQuantity);\n\n        const quantity = await this.waitFor(this.services['cart'].add({\n            productTemplateId: productTemplateId,\n            productId: productId,\n            isCombo: isCombo,\n            ptavs: ptavs,\n        }, {\n            isConfigured: false, // Custom attributes may still require configuration.\n            redirectToCart: false,\n            showQuantity: showQuantity,\n        }));\n\n        if (quantity > 0) {\n            await this._removeProduct(button, '/shop/cart');\n        }\n    }\n\n    /**\n     * Remove a product from the wishlist.\n     *\n     * @param {Element} button The button that triggered the removal.\n     * @param {String} emptyRedirectUrl The URL to redirect to if the wishlist is empty.\n     */\n    async _removeProduct(button, emptyRedirectUrl) {\n        const article = button.closest('article');\n        const wish = article.dataset.wishId;\n        const productId = parseInt(article.dataset.productId);\n\n        await this.waitFor(rpc(`/shop/wishlist/remove/${wish}`));\n        article.style.display = 'none';\n\n        wishlistUtils.removeWishlistProduct(productId);\n        wishlistUtils.updateWishlistView();\n        if (!wishlistUtils.getWishlistProductIds().length && emptyRedirectUrl) {\n            redirect(emptyRedirectUrl);\n        }\n        wishlistUtils.updateWishlistNavBar();\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale_wishlist.product_wishlist', ProductWishlist);\n", "import { Interaction } from '@web/public/interaction';\nimport { registry } from '@web/core/registry';\nimport { rpc } from '@web/core/network/rpc';\nimport wishlistUtils from '@website_sale_wishlist/js/website_sale_wishlist_utils';\n\nexport class WishlistNavbar extends Interaction {\n    static selector = '.o_wsale_my_wish';\n\n    /**\n     * Refresh the products in the wishlist.\n     */\n    async willStart() {\n        const wishCount = parseInt(this.el.querySelector('.my_wish_quantity')?.textContent);\n        if (wishlistUtils.getWishlistProductIds().length !== wishCount) {\n            wishlistUtils.setWishlistProductIds(\n                await this.waitFor(rpc('/shop/wishlist/get_product_ids'))\n            );\n        }\n    }\n\n    /**\n     * Update the wishlist navbar.\n     */\n    start() {\n        wishlistUtils.updateWishlistNavBar();\n    }\n}\n\nregistry\n    .category('public.interactions')\n    .add('website_sale_wishlist.wishlist_navbar', WishlistNavbar);\n", "const WISHLIST_PRODUCT_IDS_SESSION_NAME = 'wishlist_product_ids';\n\n/**\n * Get the IDs of the products in the wishlist from the session.\n *\n * @return {Array<number>} The IDs of the products in the wishlist.\n */\nfunction getWishlistProductIds() {\n    return JSON.parse(sessionStorage.getItem(WISHLIST_PRODUCT_IDS_SESSION_NAME) || '[]');\n}\n\n/**\n * Set the IDs of the products in the wishlist in the session.\n *\n * @param {ArrayLike<number>} productIds The IDs of the products in the wishlist.\n */\nfunction setWishlistProductIds(productIds) {\n    sessionStorage.setItem(\n        WISHLIST_PRODUCT_IDS_SESSION_NAME, JSON.stringify(Array.from(productIds))\n    );\n}\n\n/**\n * Add the specified product to the wishlist.\n *\n * @param {number} productId\n */\nfunction addWishlistProduct(productId) {\n    const productIds = new Set(getWishlistProductIds());\n    productIds.add(productId);\n    setWishlistProductIds(productIds);\n}\n\n/**\n * Remove the specified product from the wishlist.\n *\n * @param {number} productId\n */\nfunction removeWishlistProduct(productId) {\n    const productIds = new Set(getWishlistProductIds());\n    productIds.delete(productId);\n    setWishlistProductIds(productIds);\n}\n\n/**\n * Update the visibility and quantity of the wishlist button in the navbar.\n */\nfunction updateWishlistNavBar() {\n    const wishlistProductIds = getWishlistProductIds();\n    const wishButtons = document.querySelectorAll('.o_wsale_my_wish');\n    wishButtons.forEach(button => {\n        if (button.classList.contains('o_wsale_my_wish_hide_empty')) {\n            button.classList.toggle('d-none', !wishlistProductIds.length);\n        }\n        button.querySelector('.my_wish_quantity').textContent = `${wishlistProductIds.length}`;\n    });\n    const wishlistQuantities = document.querySelectorAll('.my_wish_quantity');\n    wishlistQuantities.forEach(quantity => {\n        quantity.classList.toggle('d-none', !wishlistProductIds.length);\n    });\n}\n\nfunction updateWishlistView() {\n    const wishlistProductIDs = getWishlistProductIds();\n    const wishlistEmptyEl = document.getElementById('empty-wishlist-message');\n    wishlistEmptyEl.classList.toggle('d-none', wishlistProductIDs.length > 0);\n}\n\n/**\n * Update the disabled/enabled state of an element.\n *\n * @param {Element} el The element to disable/enable.\n * @param {boolean} isDisabled Whether the element should be disabled.\n */\nfunction updateDisabled(el, isDisabled) {\n    el.disabled = isDisabled;\n    el.classList.toggle('disabled', isDisabled);\n}\n\nexport default {\n    getWishlistProductIds: getWishlistProductIds,\n    setWishlistProductIds: setWishlistProductIds,\n    addWishlistProduct: addWishlistProduct,\n    removeWishlistProduct: removeWishlistProduct,\n    updateWishlistNavBar: updateWishlistNavBar,\n    updateDisabled: updateDisabled,\n    updateWishlistView: updateWishlistView,\n};\n", "import { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\nimport { ProductComparison } from '@website_sale_comparison/interactions/product_comparison';\nimport comparisonUtils from '@website_sale_comparison/js/website_sale_comparison_utils';\n\npatch(ProductComparison.prototype, {\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            '.wishlist-section .o_add_to_compare': {\n                't-on-click': this.addProductFromWishlist.bind(this),\n            },\n        });\n    },\n\n    /**\n     * Add a product to the comparison from the wishlist page.\n     *\n     * @param {Event} ev\n     */\n    addProductFromWishlist(ev) {\n        if (this._checkMaxComparisonProducts()) return;\n\n        const el = ev.currentTarget;\n        const productId = parseInt(el.dataset.productId);\n        if (!productId || this._checkProductAlreadyInComparison(productId)) {\n            comparisonUtils.updateDisabled(el, true);\n            return;\n        }\n\n        comparisonUtils.addComparisonProduct(productId, this.bus);\n        comparisonUtils.updateDisabled(el, true);\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\nimport { rpc } from '@web/core/network/rpc';\nimport { Checkout } from '@website_sale/interactions/checkout';\n\n// temporary for OnNoResultReturned bug\nimport { registry } from '@web/core/registry';\nimport { ThirdPartyScriptError } from '@web/core/errors/error_service';\nconst errorHandlerRegistry = registry.category('error_handlers');\n\nfunction corsIgnoredErrorHandler(env, error) {\n    if (error instanceof ThirdPartyScriptError) {\n        return true;\n    }\n}\n\npatch(Checkout.prototype, {\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            '#btn_confirm_relay': { 't-on-click': this.onClickBtnConfirmRelay.bind(this) },\n        });\n        this.mondialRelayModal = undefined;\n        this.useDeliveryAsBillingTooltip = undefined;\n        const useDeliveryAsBillingLabel = this.el.querySelector('#use_delivery_as_billing_label');\n        if (useDeliveryAsBillingLabel) {\n            this.useDeliveryAsBillingTooltip = window.Tooltip\n                .getOrCreateInstance(useDeliveryAsBillingLabel);\n            this.registerCleanup(() => this.useDeliveryAsBillingTooltip.dispose());\n        }\n        this._adaptUseDeliveryAsBillingToggle();\n    },\n\n    /**\n     * If the Mondial Relay delivery method is selected, uncheck the \"use delivery as billing\"\n     * toggle and show the Mondial Relay modal.\n     *\n     * @override method from `@website_sale/interactions/checkout`\n     */\n    async selectDeliveryMethod(ev) {\n        const checkedRadio = ev.currentTarget;\n        await this.waitFor(super.selectDeliveryMethod(...arguments));\n        if (checkedRadio.dataset.isMondialrelay) {\n            if (this.useDeliveryAsBillingToggle?.checked) {\n                // Uncheck the \"use delivery as billing\" toggle and show the billing address.\n                this.useDeliveryAsBillingToggle.dispatchEvent(new MouseEvent('click'));\n            }\n            // Fetch delivery method data.\n            const result = await this.waitFor(this._setDeliveryMethod(checkedRadio.dataset.dmId));\n            // Show the Mondial Relay modal.\n            if (!this.mondialRelayModal) {\n                this._loadMondialRelayModal(result);\n            } else {\n                this.mondialRelayModal.querySelector('#btn_confirm_relay').classList.toggle(\n                    'disabled', !result.mondial_relay.current\n                );\n                window.Modal.getOrCreateInstance(this.mondialRelayModal).show();\n            }\n        }\n        this._adaptUseDeliveryAsBillingToggle();\n    },\n\n    /**\n     * If a Mondial Relay address is selected, uncheck the \"use delivery as billing\" toggle and show\n     * the billing address. Mondial Relay addresses can't be used as billing addresses.\n     *\n     * @override method from `@website_sale/interactions/checkout`\n     */\n    async changeAddress(ev) {\n        const newAddress = ev.currentTarget;\n        if (newAddress.dataset.isMondialrelay && this.useDeliveryAsBillingToggle?.checked) {\n            // Uncheck the \"use delivery as billing\" toggle and show the billing address.\n            this.useDeliveryAsBillingToggle.dispatchEvent(new MouseEvent('click'));\n        }\n        await this.waitFor(super.changeAddress(...arguments));\n        this._adaptUseDeliveryAsBillingToggle();\n    },\n\n    /**\n     * Disable the \"use delivery as billing\" toggle iff the Mondial Relay delivery method is\n     * selected, or a Mondial Relay address is selected.\n     *\n     * @private\n     * @return {void}\n     */\n    _adaptUseDeliveryAsBillingToggle() {\n        if (this.useDeliveryAsBillingToggle) {\n            const checkedRadio = document.querySelector('input[name=\"o_delivery_radio\"]:checked');\n            const selectedDeliveryAddress = this._getSelectedAddress('delivery');\n            const requireSeparateBillingAddress = (\n                checkedRadio?.dataset.isMondialrelay\n                || selectedDeliveryAddress?.dataset.isMondialrelay\n            );\n            this.useDeliveryAsBillingToggle.disabled = requireSeparateBillingAddress;\n            requireSeparateBillingAddress\n                ? this.useDeliveryAsBillingTooltip?.enable()\n                : this.useDeliveryAsBillingTooltip?.disable();\n        }\n    },\n\n    /**\n     * Render the Mondial Relay modal, using the information from `result`, and insert it in the\n     * DOM.\n     *\n     * @private\n     * @param {Object} result data about the selected delivery method.\n     */\n    _loadMondialRelayModal(result) {\n        // add modal to body and bind 'save' button\n        this.renderAt('website_sale_mondialrelay', {}, document.querySelector('body'));\n        this.mondialRelayModal = document.querySelector('#modal_mondialrelay');\n        this.mondialRelayModal.querySelector('#btn_confirm_relay').addEventListener(\n            'click', this.onClickBtnConfirmRelay.bind(this)\n        );\n\n        // load mondial relay script\n        const script = document.createElement('script');\n        script.src = \"https://widget.mondialrelay.com/parcelshop-picker/jquery.plugin.mondialrelay.parcelshoppicker.min.js\";\n        script.onload = () => {\n            // instanciate MondialRelay widget\n            const params = {\n                Target: \"\", // required but handled by OnParcelShopSelected\n                Brand: result.mondial_relay.brand,\n                ColLivMod: result.mondial_relay.col_liv_mod,\n                AllowedCountries: result.mondial_relay.allowed_countries,\n                Country: result.mondial_relay.partner_country_code,\n                PostCode: result.mondial_relay.partner_zip,\n                Responsive: true,\n                ShowResultsOnMap: true,\n                AutoSelect: result.mondial_relay.current,\n                OnParcelShopSelected: (RelaySelected) => {\n                    this.lastRelaySelected = RelaySelected;\n                    this.mondialRelayModal.querySelector('#btn_confirm_relay').classList.remove(\n                        'disabled'\n                    );\n                },\n                OnNoResultReturned: () => {\n                    // HACK while Mondial Relay fix his bug\n                    // disable corsErrorHandler for 10 seconds\n                    // If code postal not valid, it will crash with Cors Error:\n                    // Cannot read property 'on' of undefined at u.MR_FitBounds\n                    const randInt = Math.floor(Math.random() * 100);\n                    errorHandlerRegistry.add(\"corsIgnoredErrorHandler\" + randInt, corsIgnoredErrorHandler, {sequence: 10});\n                    this.waitForTimeout(\n                        () => errorHandlerRegistry.remove(\"corsIgnoredErrorHandler\" + randInt),\n                        10000,\n                    );\n                },\n            };\n            const zoneWidget = this.mondialRelayModal.querySelector('#o_zone_widget');\n            $(zoneWidget).MR_ParcelShopPicker(params);\n            window.Modal.getOrCreateInstance(this.mondialRelayModal).show();\n            zoneWidget.dispatchEvent(new Event('MR_RebindMap'));\n        };\n        document.body.appendChild(script);\n    },\n\n    /**\n     * Update the shipping address on the order and refresh the UI.\n     */\n    async onClickBtnConfirmRelay() {\n        if (!this.lastRelaySelected) return;\n        await this.waitFor(rpc('/website_sale_mondialrelay/update_shipping', {\n            ...this.lastRelaySelected,\n        }));\n        location.reload(); // Update the addresses.\n    },\n});\n", "import { rpc } from '@web/core/network/rpc';\nimport { isEmail } from '@web/core/utils/strings';\nimport { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\nimport { WebsiteSale } from '@website_sale/interactions/website_sale';\n\npatch(WebsiteSale.prototype, {\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            '#product_stock_notification_message': {\n                't-on-click': this.onClickProductStockNotificationMessage.bind(this),\n            },\n            '#product_stock_notification_form_submit_button': {\n                't-on-click': this.onClickSubmitProductStockNotificationForm.bind(this),\n            },\n        });\n    },\n\n    onClickProductStockNotificationMessage(ev) {\n        const partnerEmail = document.querySelector('#wsale_user_email').value;\n        const emailInputEl = document.querySelector('#stock_notification_input');\n\n        emailInputEl.value = partnerEmail;\n        this._handleClickStockNotificationMessage(ev);\n    },\n\n    onClickSubmitProductStockNotificationForm(ev) {\n        const formEl = ev.currentTarget.closest('#stock_notification_form');\n        const productId = parseInt(formEl.querySelector('input[name=\"product_id\"]').value);\n        this._handleClickSubmitStockNotificationForm(ev, productId);\n    },\n\n    _handleClickStockNotificationMessage(ev) {\n        ev.currentTarget.classList.add('d-none');\n        ev.currentTarget.parentElement.querySelector('#stock_notification_form').classList.remove('d-none');\n    },\n\n    async _handleClickSubmitStockNotificationForm(ev, productId) {\n        const stockNotificationEl = ev.currentTarget.closest('#stock_notification_div');\n        const formEl = stockNotificationEl.querySelector('#stock_notification_form');\n        const email = stockNotificationEl.querySelector('#stock_notification_input').value.trim();\n\n        if (!isEmail(email)) {\n            return this._displayEmailIncorrectMessage(stockNotificationEl);\n        }\n\n        try {\n            await this.waitFor(rpc(\n                '/shop/add/stock_notification', { product_id: productId, email }\n            ));\n        } catch {\n            this._displayEmailIncorrectMessage(stockNotificationEl);\n            return;\n        }\n        const message = stockNotificationEl.querySelector('#stock_notification_success_message');\n        message.classList.remove('d-none');\n        formEl.classList.add('d-none');\n    },\n\n    _displayEmailIncorrectMessage(stockNotificationEl) {\n        const incorrectIconEl = stockNotificationEl.querySelector('#stock_notification_input_incorrect');\n        incorrectIconEl.classList.remove('d-none');\n    },\n\n    /**\n     * Adds the stock checking to the regular _onChangeCombination method\n     * @override\n     */\n    _onChangeCombination(ev, parent, combination) {\n        super._onChangeCombination(...arguments);\n        this._onChangeCombinationStock(...arguments);\n    },\n\n    /**\n     * Recomputes the combination after adding a product to the cart\n     */\n    async onClickAdd(ev) {\n        const quantity = await this.waitFor(super.onClickAdd(...arguments));\n        if (this.el.querySelector('div.availability_messages')) {\n            await this.waitFor(this._getCombinationInfo(ev));\n        }\n        return quantity;\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport {\n    ComboConfiguratorDialog\n} from '@sale/js/combo_configurator_dialog/combo_configurator_dialog';\n\npatch(ComboConfiguratorDialog.prototype, {\n    async selectComboItem(comboId, comboItem) {\n        if (!comboItem.product.isQuantityAllowed(this.state.quantity)) {\n            return;\n        }\n        super.selectComboItem(...arguments);\n    },\n\n    async setQuantity(quantity) {\n        if (!this.isComboQuantityAllowed(quantity)) {\n            quantity = Math.min(\n                ...this._selectedComboItems\n                    .map(comboItem => comboItem.product.free_qty)\n                    .filter(freeQty => freeQty !== undefined)\n            );\n        }\n        return super.setQuantity(quantity);\n    },\n\n    /**\n     * Check whether the provided combo quantity can be added to the cart.\n     *\n     * @param {Number} quantity The quantity to check.\n     * @return {Boolean} Whether the combo quantity can be added to the cart.\n     */\n    isComboQuantityAllowed(quantity) {\n        return this._selectedComboItems.every(\n            comboItem => comboItem.product.isQuantityAllowed(quantity)\n        );\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport { ProductProduct } from '@sale/js/models/product_product';\n\npatch(ProductProduct.prototype, {\n    /**\n     * @param {number} free_qty\n     * @param args Super's parameter list.\n     */\n    setup({free_qty, ...args}) {\n        super.setup(args);\n        this.free_qty = free_qty;\n    },\n\n    /**\n     * Check whether the provided quantity can be added to the cart.\n     *\n     * @param {Number} quantity The quantity to check.\n     * @return {Boolean} Whether the product quantity can be added to the cart.\n     */\n    isQuantityAllowed(quantity) {\n        return this.free_qty === undefined || this.free_qty >= quantity;\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport { Product } from '@sale/js/product/product';\n\npatch(Product, {\n    props: {\n        ...Product.props,\n        free_qty: { type: Number, optional: true },\n    },\n});\n\npatch(Product.prototype, {\n    /**\n     * Check whether this product is out of stock.\n     *\n     * @return {Boolean} - Whether this product is out of stock.\n     */\n    isOutOfStock() {\n        return !this.env.isQuantityAllowed(this.props, 1);\n    },\n});\n", "import { _t } from '@web/core/l10n/translation';\nimport { patch } from '@web/core/utils/patch';\nimport { ProductCard } from '@sale/js/product_card/product_card';\n\npatch(ProductCard, {\n    props: {\n        ...ProductCard.props,\n        quantity: { type: Number, optional: true },\n    },\n});\n\npatch(ProductCard.prototype, {\n    setup() {\n        super.setup(...arguments);\n        this.allQuantitySelectedTooltip = _t(\"All available quantity selected\");\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport { useSubEnv } from '@odoo/owl';\nimport {\n    ProductConfiguratorDialog\n} from '@sale/js/product_configurator_dialog/product_configurator_dialog';\n\npatch(ProductConfiguratorDialog.prototype, {\n    setup() {\n        super.setup(...arguments);\n\n        useSubEnv({\n            isQuantityAllowed: this._isQuantityAllowed.bind(this),\n        });\n    },\n\n    async _setQuantity(productTmplId, quantity) {\n        const product = this._findProduct(productTmplId);\n        if (!this._isQuantityAllowed(product, quantity)) {\n            quantity = product.free_qty;\n        }\n        return super._setQuantity(productTmplId, quantity);\n    },\n\n    /**\n     * Check whether the provided product quantity can be added to the cart.\n     *\n     * @param {Object} product - The provided product.\n     * @param {Number} quantity - The new quantity of the product.\n     * @return {Boolean} - Whether the provided product quantity can be added to the cart.\n     */\n    _isQuantityAllowed(product, quantity) {\n        return !('free_qty' in product) || product.free_qty >= quantity;\n    },\n\n    /**\n     * Check whether all selected product quantities can be added to the cart.\n     *\n     * @return {Boolean} - Whether all selected product quantities can be added to the cart.\n     */\n    areQuantitiesAllowed() {\n        return this.state.products.every(p => this._isQuantityAllowed(p, p.quantity));\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport {\n    AddProductToWishlistButton\n} from '@website_sale_wishlist/interactions/add_product_to_wishlist_button';\n\n// TODO(loti): this doesn't work since interactions aren't restarted when the \"save for later\"\n// button is appended to the template. It will be fixed once the variant mixin has been removed.\npatch(AddProductToWishlistButton.prototype, {\n    /**\n     * Remove wishlist indication when adding a product to the wishlist.\n     */\n    async addProduct(ev) {\n        await this.waitFor(super.addProduct(...arguments));\n        const saveForLaterButton = document.querySelector('#wsale_save_for_later_button');\n        const addedToWishListAlert = document.querySelector('#wsale_added_to_your_wishlist_alert');\n        if (saveForLaterButton) {\n            saveForLaterButton.classList.add('d-none');\n            addedToWishListAlert.classList.remove('d-none');\n        }\n    },\n});\n", "import { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\nimport { WebsiteSale } from '@website_sale/interactions/website_sale';\n\npatch(WebsiteSale.prototype, {\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            '#wishlist_stock_notification_message': {\n                't-on-click': this.onClickWishlistStockNotificationMessage.bind(this),\n            },\n            '#wishlist_stock_notification_form_submit_button': {\n                't-on-click': this.onClickSubmitWishlistStockNotificationForm.bind(this),\n            },\n        });\n    },\n\n    onClickWishlistStockNotificationMessage(ev) {\n        this._handleClickStockNotificationMessage(ev);\n    },\n\n    onClickSubmitWishlistStockNotificationForm(ev) {\n        const productId = ev.currentTarget.closest('article').dataset.productId;\n        this._handleClickSubmitStockNotificationForm(ev, productId);\n    },\n});\n", "import { registry } from \"@web/core/registry\";\nimport { PostLink } from \"@website/interactions/post_link\";\n\n// TODO: remove in master\nexport class BlogPostLink extends PostLink {\n    static selector = \"select[name='archive'], span:has(.fa-calendar-o) a\";\n}\n\nregistry.category(\"public.interactions\").add(\"website_blog.blog_post_link\", BlogPostLink);\n", "import { scrollTo } from \"@html_builder/utils/scrolling\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { verifyHttpsUrl } from \"@website/utils/misc\";\nimport { getActiveHotkey } from \"@web/core/hotkeys/hotkey_service\";\n\nexport class WebsiteBlog extends Interaction {\n    static selector = \".website_blog\";\n    dynamicContent = {\n        \".o_wblog_next_button\": {\n            \"t-on-click.prevent\": this.onNextBlogClick,\n            \"t-on-keydown\": this.onNextBlogKeydown,\n        },\n        \"#o_wblog_post_content_jump\": {\n            \"t-on-click.prevent.withTarget\": this.onContentAnchorClick,\n        },\n        \".o_twitter, .o_facebook, .o_linkedin, .o_google, .o_twitter_complete, .o_facebook_complete, .o_linkedin_complete, .o_google_complete\":\n            {\n                \"t-on-click.prevent.withTarget\": this.onShareArticleClick,\n            },\n    };\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    async onNextBlogClick(ev) {\n        const blogNextContainerEl = ev.currentTarget.closest(\"#o_wblog_next_container\");\n        const nextInfo = blogNextContainerEl.querySelector(\"#o_wblog_next_post_info\").dataset;\n        const recordCoverContainerEl = blogNextContainerEl.querySelector(\n            \".o_record_cover_container\"\n        );\n        const classes = nextInfo.size.split(\" \");\n        recordCoverContainerEl.classList.add(...classes, nextInfo.textContent);\n        blogNextContainerEl\n            .querySelectorAll(\".o_wblog_toggle\")\n            .forEach((el) => el.classList.toggle(\"d-none\"));\n        // Appending a placeholder so that the cover can scroll to the top of the\n        // screen, regardless of its height.\n        const placeholder = document.createElement(\"div\");\n        placeholder.style.minHeight = \"100vh\";\n        this.insert(placeholder, this.el.querySelector(\"#o_wblog_next_container\"), \"beforeend\");\n        const nextUrl = verifyHttpsUrl(nextInfo.url);\n        await this.forumScrollAction(\n            blogNextContainerEl,\n            300,\n            () => (browser.location.href = nextUrl)\n        );\n    }\n    /**\n     * @param {KeyboardEvent} ev\n     */\n    onNextBlogKeydown(ev) {\n        const hotkey = getActiveHotkey(ev);\n        if (hotkey === \"enter\" || hotkey === \"space\") {\n            return this.onNextBlogClick(ev);\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onContentAnchorClick(ev, currentTargetEl) {\n        ev.stopImmediatePropagation();\n        const scrollTargetEl = document.querySelector(currentTargetEl.hash);\n\n        await this.forumScrollAction(\n            scrollTargetEl,\n            500,\n            () => (browser.location.hash = \"blog_content\")\n        );\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onShareArticleClick(ev, currentTargetEl) {\n        let url = \"\";\n        const blogPostTitle = document.querySelector(\".o_wblog_post_name\").textContent || \"\";\n        const articleURL = browser.location.href;\n        if (currentTargetEl.classList.contains(\"o_twitter\")) {\n            const tweetText = _t(\"Amazing blog article: %(title)s! Check it live: %(url)s\", {\n                title: blogPostTitle,\n                url: articleURL,\n            });\n            url =\n                \"https://twitter.com/intent/tweet?tw_p=tweetbutton&text=\" +\n                encodeURIComponent(tweetText);\n        } else if (currentTargetEl.classList.contains(\"o_facebook\")) {\n            url = \"https://www.facebook.com/sharer/sharer.php?u=\" + encodeURIComponent(articleURL);\n        } else if (currentTargetEl.classList.contains(\"o_linkedin\")) {\n            url =\n                \"https://www.linkedin.com/sharing/share-offsite/?url=\" +\n                encodeURIComponent(articleURL);\n        }\n        window.open(url, \"\", \"menubar=no, width=500, height=400\");\n    }\n\n    /**\n     * @param {HTMLElement} el - the element we are scrolling to\n     * @param {Integer} duration - scroll animation duration\n     * @param {Function} callback - to be executed after the scroll is performed\n     */\n    async forumScrollAction(el, duration, callback) {\n        await this.waitFor(scrollTo(el, { duration }));\n        callback();\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website_blog.website_blog\", WebsiteBlog);\n", "import { DynamicSnippet } from \"@website/snippets/s_dynamic_snippet/dynamic_snippet\";\nimport { registry } from \"@web/core/registry\";\n\nexport class BlogPosts extends DynamicSnippet {\n    static selector = \".s_dynamic_snippet_blog_posts\";\n\n    /**\n     * @override\n     */\n    getSearchDomain() {\n        const searchDomain = super.getSearchDomain(...arguments);\n        const filterByBlogId = parseInt(this.el.dataset.filterByBlogId);\n        if (filterByBlogId >= 0) {\n            searchDomain.push([\"blog_id\", \"=\", filterByBlogId]);\n        }\n        return searchDomain;\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website_blog.blog_posts\", BlogPosts);\n\nregistry.category(\"public.interactions.edit\").add(\"website_blog.blog_posts\", {\n    Interaction: BlogPosts,\n});\n", "import { scrollTo, closestScrollable } from \"@html_builder/utils/scrolling\";\nimport { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\nimport { markup } from \"@odoo/owl\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { ConfirmationDialog } from \"@web/core/confirmation_dialog/confirmation_dialog\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { htmlJoin } from \"@web/core/utils/html\";\nimport { session } from \"@web/session\";\nimport { FlagMarkAsOffensiveDialog } from \"../components/flag_mark_as_offensive/flag_mark_as_offensive\";\nimport { WebsiteForumTagsWrapper } from \"../components/website_forum_tags_wrapper\";\nimport { WebsiteForumWysiwyg } from \"@website_forum/components/website_forum_wysiwyg/website_forum_wysiwyg\";\nimport { isMobileOS } from \"@web/core/browser/feature_detection\";\n\nexport class WebsiteForum extends Interaction {\n    static selector = \".website_forum\";\n    dynamicContent = {\n        \".karma_required\": {\n            \"t-on-click.withTarget\": this.onKarmaRequiredClick,\n        },\n        \".o_js_forum_tag_follow\": {\n            \"t-on-click.withTarget\": this.onTagFollowClick,\n        },\n        \".o_wforum_flag:not(.karma_required)\": {\n            \"t-on-click.prevent.withTarget\": this.onFlagClick,\n        },\n        \".o_wforum_flag_validator\": {\n            \"t-on-click.prevent.withTarget\": this.onFlagValidatorClick,\n        },\n        \".o_wforum_flag_mark_as_offensive\": {\n            \"t-on-click.prevent.withTarget\": this.onFlagMarkAsOffensiveClick,\n        },\n        \".vote_up:not(.karma_required), .vote_down:not(.karma_required)\": {\n            \"t-on-click.prevent.withTarget\": this.onVotePostClick,\n        },\n        \".o_wforum_validation_queue a[href*='/validate']\": {\n            \"t-on-click.prevent.withTarget\": this.onValidationQueueClick,\n        },\n        \".o_wforum_validate_toggler:not(.karma_required)\": {\n            \"t-on-click.prevent.withTarget\": this.onToggleValidateClick,\n        },\n        \".o_wforum_favourite_toggle\": {\n            \"t-on-click.prevent.withTarget\": this.onToggleFavouriteClick,\n        },\n        \".comment_delete:not(.karma_required)\": {\n            \"t-on-click.prevent.withTarget\": this.onDeleteCommentClick,\n        },\n        \".js_close_intro\": {\n            \"t-on-click.prevent\": this.onCloseIntroClick,\n        },\n        \".answer_collapse\": {\n            \"t-on-click.withTarget\": this.onExpandAnswerClick,\n        },\n        \".js_wforum_submit_form:has(.o_wforum_submit_post:not(.karma_required))\": {\n            \"t-on-submit.withTarget\": this.onFormSubmit,\n        },\n        \"#post_reply\": {\n            \"t-on-shown.bs.collapse.withTarget\": this.onCollapseShown,\n        },\n        // Not sure this is still needed.\n        // float-start class messes up the post layout OPW 769721\n        \"span[data-oe-model='forum.post'][data-oe-field='content'] img.float-start\": {\n            \"t-att-class\": () => ({ \"float-start\": false }),\n        },\n    };\n\n    setup() {\n        this.lastsearch = [];\n\n        // welcome message action button\n        const forumRegisterUrlEl = this.el.querySelector(\".forum_register_url\");\n        if (forumRegisterUrlEl) {\n            const forumLogin = `${browser.location.origin}/odoo?redirect=${encodeURIComponent(browser.location.href)}`;\n            forumRegisterUrlEl.href = forumLogin;\n        }\n\n        // Initialize forum's tooltips\n        this.el.querySelectorAll(\"[data-bs-toggle='tooltip']\").forEach((el) => {\n            const bsTooltip = window.Tooltip.getOrCreateInstance(el);\n            this.registerCleanup(() => bsTooltip.dispose());\n        });\n\n        this.el.querySelectorAll(\"[data-bs-toggle='popover']\").forEach((el) => {\n            const bsPopover = window.Popover.getOrCreateInstance(el);\n            this.registerCleanup(() => bsPopover.dispose());\n        });\n\n        const selectMenuWrapperEl = document.querySelector(\"div.js_select_menu_wrapper\");\n        if (selectMenuWrapperEl) {\n            const isReadOnly = Boolean(selectMenuWrapperEl.dataset.readonly);\n            // Take default tags from the input value\n            const defaulValue = JSON.parse(selectMenuWrapperEl.dataset.initValue || \"[]\").map((x) => x.id);\n\n            this.mountComponent(selectMenuWrapperEl, WebsiteForumTagsWrapper, {\n                defaulValue: defaulValue,\n                disabled: isReadOnly,\n            });\n        }\n\n        this.el.querySelectorAll(\"textarea.o_wysiwyg_loader\").forEach((textareaEl) => {\n            const editorKarma = parseInt(textareaEl.dataset.karma || 0); // default value for backward compatibility\n            const hasFullEdit = parseInt(this.el.querySelector(\"#karma\").value) >= editorKarma;\n            const isReply = !!textareaEl.closest(\"#post_reply\");\n            const props = {\n                textareaEl,\n                fullEdit: hasFullEdit,\n                getRecordInfo: () => ({\n                    context: this.services.website_page.context,\n                    resModel: \"forum.post\",\n                    // Id is retrieved from URL, which is either:\n                    // - /forum/name-1/post/something-5\n                    // - /forum/name-1/post/something-5/edit\n                    // TODO: Make this more robust.\n                    resId: +browser.location.pathname.split(\"-\").slice(-1)[0].split(\"/\")[0],\n                }),\n                ...(!isReply && {\n                    resizable: !isMobileOS(),\n                    height: \"350px\",\n                }),\n            };\n            const wysiwygWrapper = textareaEl.closest(\".o_wysiwyg_textarea_wrapper\");\n            textareaEl.style.display = \"none\";\n            wysiwygWrapper.after(textareaEl);\n            wysiwygWrapper.replaceChildren();\n\n            this.mountComponent(wysiwygWrapper, WebsiteForumWysiwyg, props);\n        });\n\n        this.el.querySelectorAll(\".o_wforum_bio_popover\").forEach((authorBox) => {\n            const bsPopover = window.Popover.getOrCreateInstance(authorBox, {\n                trigger: \"hover\",\n                offset: \"10\",\n                animation: false,\n                html: true,\n                customClass: \"o_wforum_bio_popover_container shadow-sm\",\n            });\n            this.registerCleanup(() => bsPopover.dispose());\n        });\n\n        this.el.querySelectorAll(\".o_wforum_question, .o_wforum_answer, .o_wforum_post_comment, .o_wforum_last_activity\")\n            .forEach((post) => {\n                post.querySelector(\".o_wforum_relative_datetime\").textContent =\n                    luxon.DateTime.fromSQL(post.dataset.lastActivity, { zone: \"utc\" }).toRelative();\n            });\n    }\n\n    /**\n     * Check if the user is public, if it's true send a warning alert saying the\n     * action cannot be performed.\n     *\n     * @returns {boolean}\n     **/\n    warnIfPublicUser() {\n        if (session.is_website_user) {\n            this.displayAccessDeniedNotification(\n                markup`<a href='/web/login'>${_t(\n                    \"Oh no! Please sign in to perform this action\"\n                )}</a>`\n            );\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * @param {string} message\n     */\n    displayAccessDeniedNotification(message) {\n        this.services.notification.add(message, {\n            title: _t(\"Access Denied\"),\n            sticky: false,\n            type: \"warning\",\n        });\n    }\n\n    /**\n     * @param {SubmitEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onFormSubmit(ev, currentTargetEl) {\n        let validForm = true;\n        const titleEl = currentTargetEl.querySelector(\"input[name=post_name]\");\n        const textareaEl = currentTargetEl.querySelector(\"textarea[name=content]\");\n\n        if (titleEl?.required) {\n            titleEl.classList.toggle(\"is-invalid\", !!titleEl.value);\n            validForm = !!titleEl.value;\n        }\n\n        // Because the textarea is hidden, we add the red or green border to its\n        // container.\n        if (textareaEl?.required) {\n            const textareaContainerEl = currentTargetEl.querySelector(\".o_wysiwyg_textarea_wrapper\");\n            const hasContent = !!textareaContainerEl.innerText.trim() || !!textareaContainerEl.querySelector(\"img\");\n            [\"border\", \"border-danger\", \"rounded-top\"].forEach((cls) => {\n                textareaContainerEl.classList.toggle(cls, hasContent);\n            });\n            validForm = hasContent;\n        }\n\n        if (validForm) {\n            // Stores social share data to display modal on next page.\n            if (currentTargetEl.querySelector(\".oe_social_share_call\")) {\n                sessionStorage.setItem(\"social_share\", JSON.stringify({\n                    targetType: currentTargetEl.querySelector(\".o_wforum_submit_post\").dataset.socialTargetType,\n                }));\n            }\n        } else {\n            ev.preventDefault();\n            this.waitForTimeout(() => {\n                currentTargetEl.querySelectorAll(\"button[type='submit'], a.a-submit\").forEach((btnEl) => {\n                    btnEl.querySelector(\"i\").remove();\n                    btnEl.disabled = false;\n                });\n            }, 0);\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onExpandAnswerClick(ev, currentTargetEl) {\n        if (ev.target.matches(\".o_wforum_expand_toggle\")) {\n            currentTargetEl.classList.toggle(\"o_expand\");\n            currentTargetEl.classList.toggle(\"min-vh-100\");\n            currentTargetEl.classList.toggle(\"w-lg-50\");\n        } else if (ev.target.matches(\".o_wforum_discard_btn\")) {\n            currentTargetEl.classList.remove(\"o_expand\", \"min-vh-100\");\n            currentTargetEl.classList.add(\"w-lg-50\");\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onKarmaRequiredClick(ev, currentTargetEl) {\n        const karma = parseInt(currentTargetEl.dataset.karma);\n        if (!karma) {\n            return;\n        }\n        ev.preventDefault();\n        if (this.warnIfPublicUser()) {\n            return;\n        }\n        const forumId = parseInt(this.el.ownerDocument.getElementById(\"wrapwrap\").dataset.forum_id);\n        let message = _t(\"%(score)s karma is required to perform this action.\", { score: karma });\n        if (forumId) {\n            message = htmlJoin(\n                message,\n                _t(\"%(link_start)sRead the guidelines to know how to gain karma.%(link_end)s\", {\n                    link_start: markup`<br><a class=\"alert-link\" href=\"/forum/${forumId}/faq\">`,\n                    link_end: markup`</a>`,\n                })\n            );\n        }\n        this.services.notification.add(message, {\n            type: \"warning\",\n            sticky: false,\n            title: _t(\"Karma Error\"),\n        });\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onTagFollowClick(ev, currentTargetEl) {\n        if (ev.target.closest(\"button\")) {\n            currentTargetEl.querySelector(\".o_js_forum_tag_link\").classList.toggle(\"text-muted\");\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onFlagClick(ev, currentTargetEl) {\n        if (this.warnIfPublicUser()) {\n            return;\n        }\n        const data = await this.waitFor(rpc(\n            currentTargetEl.dataset.href\n            || (currentTargetEl.getAttribute(\"href\") !== \"#\" && currentTargetEl.getAttribute(\"href\"))\n            || currentTargetEl.closest(\"form\").getAttribute(\"action\")\n        ));\n        if (data.error) {\n            const message = data.error === \"post_already_flagged\"\n                ? _t(\"This post is already flagged\")\n                : data.error === \"post_non_flaggable\"\n                    ? _t(\"This post can not be flagged\")\n                    : data.error;\n            this.displayAccessDeniedNotification(message);\n        } else if (data.success) {\n            const child = currentTargetEl.firstElementChild;\n            if (data.success === \"post_flagged_moderator\") {\n                const countFlaggedPosts = this.el.querySelector(\"#count_posts_queue_flagged\");\n                currentTargetEl.innerText = _t(\" Flagged\");\n                currentTargetEl.prepend(child);\n                if (countFlaggedPosts) {\n                    countFlaggedPosts.classList.remove(\"bg-light\", \"d-none\");\n                    countFlaggedPosts.classList.add(\"text-bg-danger\");\n                    countFlaggedPosts.innerText = parseInt(countFlaggedPosts.innerText, 10) + 1;\n                }\n            } else if (data.success === \"post_flagged_non_moderator\") {\n                currentTargetEl.innerText = _t(\" Flagged\");\n                currentTargetEl.prepend(child);\n                const forumAnswerEl = currentTargetEl.closest(\".o_wforum_answer\");\n                if (forumAnswerEl) {\n                    forumAnswerEl.style.height = getComputedStyle(forumAnswerEl).height;\n                    forumAnswerEl.classList.add(\"overflow-hidden\");\n                    forumAnswerEl.style.transition = \"height 1s, opacity 1s\";\n                    forumAnswerEl.classList.add(\"opacity-0\", \"h-0\");\n                }\n            }\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onVotePostClick(ev, currentTargetEl) {\n        if (this.warnIfPublicUser()) {\n            return;\n        }\n        const data = await this.waitFor(rpc(currentTargetEl.dataset.href));\n        if (data.error) {\n            const message = data.error === \"own_post\"\n                ? _t(\"Sorry, you cannot vote for your own posts\")\n                : data.error;\n            this.displayAccessDeniedNotification(message);\n        } else {\n            const containerEl = currentTargetEl.closest(\".vote\");\n            const voteUpEl = containerEl.querySelector(\".vote_up\");\n            const voteDownEl = containerEl.querySelector(\".vote_down\");\n            const voteCountEl = containerEl.querySelector(\".vote_count\");\n            const userVote = parseInt(data[\"user_vote\"]);\n\n            voteUpEl.disabled = userVote === 1;\n            voteDownEl.disabled = userVote === -1;\n\n            [voteUpEl, voteDownEl, voteCountEl].forEach((el) => {\n                el.classList.remove(\"text-success\", \"text-danger\", \"text-muted\", \"opacity-75\", \"o_forum_vote_animate\");\n            });\n            void containerEl.offsetWidth; // Force a refresh\n\n            if (userVote === 1) {\n                voteUpEl.classList.add(\"text-success\");\n                voteCountEl.classList.add(\"text-success\");\n                voteDownEl.classList.remove(\"karma_required\");\n            }\n            if (userVote === -1) {\n                voteDownEl.classList.add(\"text-danger\");\n                voteCountEl.classList.add(\"text-danger\");\n                voteUpEl.classList.remove(\"karma_required\");\n            }\n            if (userVote === 0) {\n                voteCountEl.classList.add(\"text-muted\", \"opacity-75\");\n                if (!voteDownEl.dataset.canDownvote) {\n                    voteDownEl.classList.add(\"karma_required\");\n                }\n                if (!voteUpEl.dataset.canUpvote) {\n                    voteUpEl.classList.add(\"karma_required\");\n                }\n            }\n            voteCountEl.textContent = parseInt(data[\"vote_count\"]);\n            voteCountEl.classList.add(\"o_forum_vote_animate\");\n        }\n    }\n\n    /**\n     * Call the route to moderate/validate the post, then hide the validated post\n     * and decrement the count in the appropriate queue badge of the sidebar on success.\n     *\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onValidationQueueClick(ev, currentTargetEl) {\n        const postBeingValidated = currentTargetEl.closest(\".post_to_validate\");\n        if (!postBeingValidated) {\n            return;\n        }\n        postBeingValidated.classList.add(\"d-none\");\n        let ok;\n        try {\n            ok = (await this.waitFor(fetch(currentTargetEl.href))).ok;\n        } catch {\n            // Calling the endpoint like this returns an HTML page. As we can't\n            // extract the error message from that, we disregard it and simply\n            // restore the post's visibility. This __should__ be improved.\n        }\n        if (!ok) {\n            postBeingValidated.classList.remove(\"d-none\");\n            return;\n        }\n        const nbLeftInQueue = Array.from(document.querySelectorAll(\".post_to_validate\"))\n            .filter(e => window.getComputedStyle(e).display !== \"none\")\n            .length;\n        const queueType = document.querySelector(\"#queue_type\").dataset.queueType;\n        const queueCountBadge = document.querySelector(`#count_posts_queue_${queueType}`);\n        queueCountBadge.innerText = nbLeftInQueue;\n        if (!nbLeftInQueue) {\n            document.querySelector(\".o_caught_up_alert\").classList.remove(\"d-none\");\n            document.querySelector(\".o_wforum_btn_filter_tool\")?.classList.add(\"d-none\");\n            queueCountBadge.classList.add(\"d-none\");\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onToggleValidateClick(ev, currentTargetEl) {\n        if (this.warnIfPublicUser()) {\n            return;\n        }\n        const target = currentTargetEl.dataset.target;\n        const data = await this.waitFor(rpc(currentTargetEl.dataset.href));\n        if (data.error) {\n            const message = data.error === \"own_post\"\n                ? _t(\"Sorry, you cannot select your own posts as best answer\")\n                : data.error;\n            this.displayAccessDeniedNotification(message);\n            return;\n        }\n        for (const answer of document.querySelectorAll(\".o_wforum_answer\")) {\n            const isCorrect = answer.matches(target) ? data : false;\n            const toggler = answer.querySelector(\".o_wforum_validate_toggler\");\n            toggler.setAttribute(\n                \"data-bs-original-title\",\n                isCorrect ? toggler.dataset.helperDecline : toggler.dataset.helperAccept\n            );\n            const styleForCorrect = isCorrect ? answer.classList.add : answer.classList.remove;\n            const styleForIncorrect = isCorrect ? answer.classList.remove : answer.classList.add;\n            styleForCorrect.call(\n                answer.classList,\n                \"o_wforum_answer_correct\", \"my-2\", \"mx-n3\", \"mx-lg-n2\", \"mx-xl-n3\", \"py-3\", \"px-3\", \"px-lg-2\", \"px-xl-3\"\n            );\n            styleForIncorrect.call(toggler.classList, \"opacity-50\");\n            const answerBorder = answer.querySelector(\"div .border-start\");\n            styleForCorrect.call(answerBorder.classList, \"border-success\");\n            const togglerIcon = toggler.querySelector(\".fa\");\n            styleForCorrect.call(togglerIcon.classList, \"fa-check-circle\", \"text-success\");\n            styleForIncorrect.call(togglerIcon.classList, \"fa-check-circle-o\");\n            const correctBadge = answer.querySelector(\".o_wforum_answer_correct_badge\");\n            styleForCorrect.call(correctBadge.classList, \"d-inline\");\n            styleForIncorrect.call(correctBadge.classList, \"d-none\");\n        }\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onToggleFavouriteClick(ev, currentTargetEl) {\n        const data = await this.waitFor(rpc(currentTargetEl.dataset.href));\n        currentTargetEl.classList.toggle(\"opacity-50\", !data);\n        currentTargetEl.classList.toggle(\"opacity-100-hover\", !data);\n        const currentTargetEl_icon = currentTargetEl.querySelector(\".fa\");\n        currentTargetEl_icon.classList.toggle(\"fa-star-o\", !data);\n        currentTargetEl_icon.classList.toggle(\"o_wforum_gold\", data);\n        currentTargetEl_icon.classList.toggle(\"fa-star\", data);\n    }\n\n    /**\n     * @param {EvenMouseEventt} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onDeleteCommentClick(ev, currentTargetEl) {\n        if (this.warnIfPublicUser()) {\n            return;\n        }\n        this.services.dialog.add(ConfirmationDialog, {\n            body: _t(\"Are you sure you want to delete this comment?\"),\n            confirmLabel: _t(\"Delete\"),\n            confirm: () => {\n                rpc(currentTargetEl.closest(\"form\").attributes.action.value).then(() => {\n                    currentTargetEl.closest(\".o_wforum_post_comment\").remove();\n                }).catch((error) => {\n                    this.services.notification.add(error.data.message, {\n                        title: _t(\"Karma Error\"),\n                        sticky: false,\n                        type: \"warning\",\n                    });\n                });\n            },\n            cancel: () => { },\n        });\n    }\n\n    onCloseIntroClick() {\n        cookie.set(\"forum_welcome_message\", false, 24 * 60 * 60 * 365, \"optional\");\n        const forumIntroEl = this.el.querySelector(\".forum_intro\");\n        forumIntroEl.style.height = getComputedStyle(forumIntroEl).height;\n        forumIntroEl.classList.add(\"overflow-hidden\");\n        forumIntroEl.style.transition = \"height 1s\";\n        forumIntroEl.classList.add(\"h-0\");\n        return true;\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onFlagValidatorClick(ev, currentTargetEl) {\n        await this.waitFor(this.services.orm.call(\"forum.post\", currentTargetEl.dataset.action, [\n            parseInt(currentTargetEl.dataset.postId),\n        ]));\n        currentTargetEl.closest(\".o_wforum_flag_alert\")?.classList.toggle(\"d-none\");\n        const flaggedButton = currentTargetEl.parentElement.firstElementChild,\n            child = flaggedButton.firstElementChild,\n            countFlaggedPosts = this.el.querySelector(\"#count_posts_queue_flagged\"),\n            count = parseInt(countFlaggedPosts.innerText, 10) - 1;\n\n        flaggedButton.innerText = _t(\" Flag\");\n        flaggedButton.prepend(child);\n        if (count === 0) {\n            countFlaggedPosts.classList.add(\"bg-light\");\n        }\n        countFlaggedPosts.innerText = count;\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    async onFlagMarkAsOffensiveClick(ev, currentTargetEl) {\n        if (!/^\\/forum\\/.+?\\/ask_for_mark_as_offensive$/.test(currentTargetEl.dataset.action)) {\n            return;\n        }\n        const template = await this.waitFor(rpc(currentTargetEl.dataset.action));\n        this.services.dialog.add(FlagMarkAsOffensiveDialog, {\n            title: _t(\"Offensive Post\"),\n            body: markup(template),\n        });\n    }\n\n    /**\n     * @param {Event} ev\n     * @param {HTMLElement} currentTargetEl\n     */\n    onCollapseShown(ev, currentTargetEl) {\n        const scrollingElement = closestScrollable(currentTargetEl.parentNode);\n        scrollTo(currentTargetEl, { forcedOffset: scrollingElement.clientHeight - currentTargetEl.clientHeight });\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_forum.website_forum\", WebsiteForum);\n", "import { registry } from \"@web/core/registry\";\nimport { Interaction } from \"@web/public/interaction\";\n\nexport class WebsiteProfileForumActivity extends Interaction {\n    static selector = \".o_wprofile_forum_activity\";\n    dynamicContent = {\n        \"#o_wprofile_forum_activity_filter li a\": {\n            \"t-on-click.withTarget\": (ev, currentTargetEl) =>\n                this.selectTab(\n                    currentTargetEl.getAttribute(\"href\").split(\"_\").at(-1),\n                    currentTargetEl.text\n                ),\n        },\n        \".o_wprofile_forum_activity_search_question\": {\n            \"t-att-class\": () => ({ \"d-none\": this.activeTab !== \"question\" }),\n        },\n        \".o_wprofile_forum_activity_search_answer\": {\n            \"t-att-class\": () => ({ \"d-none\": this.activeTab !== \"answer\" }),\n        },\n        \".o_wprofile_forum_activity_filter_label\": {\n            \"t-out\": () => this.activeTabLabel,\n        },\n    };\n\n    setup() {\n        const activeTab = this.el.dataset.activeTab;\n        this.selectTab(\n            activeTab,\n            document.querySelector(\n                `#o_wprofile_forum_activity_filter li a[href='#o_wprofile_forum_activity_tab_${activeTab}']`\n            ).textContent\n        );\n    }\n\n    selectTab(tab, activeTabLabel) {\n        this.activeTab = tab;\n        this.activeTabLabel = activeTabLabel;\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_forum.wprofile_forum_activity\", WebsiteProfileForumActivity);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class WebsiteForumShare extends Interaction {\n    static selector = \".website_forum\";\n\n    start() {\n        // Retrieve stored social data\n        if (sessionStorage.getItem(\"social_share\")) {\n            const socialData = JSON.parse(sessionStorage.getItem(\"social_share\"));\n\n            if (socialData.targetType) {\n                const questionEl = document.querySelector(\".o_wforum_question\");\n                this.renderAt(\"website.social_modal\", {\n                    target_type: socialData.targetType,\n                    state: questionEl.dataset.state,\n                }, document.body, \"beforeend\", (els) => {\n                    this.addListener(els[0], \"hidden.bs.modal\", () => els[0].remove());\n                });\n                const bsModal = window.Modal.getOrCreateInstance(document.querySelector(\"#oe_social_share_modal\"));\n                bsModal.show();\n                this.registerCleanup(() => bsModal.dispose());\n            }\n\n            sessionStorage.removeItem(\"social_share\");\n        }\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_forum.website_forum_share\", WebsiteForumShare);\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\nimport { cloneContentEls } from \"@website/js/utils\";\n\nexport class WebsiteForumSpam extends Interaction {\n    static selector = \".o_wforum_moderation_queue\";\n    dynamicContent = {\n        \".o_wforum_select_all_spam\": { \"t-on-click\": this.onSelectAllSpamClick },\n        \".o_wforum_mark_spam\": { \"t-on-click\": this.locked(this.onMarkSpamClick, true) },\n        \"#spamSearch\": { \"t-on-input\": this.debounced(this.onSpamSearchInput, 200) },\n    };\n\n    setup() {\n        this.spamIDs = JSON.parse(\n            this.el.ownerDocument.querySelector(\".modal[data-spam-ids]\")?.dataset.spamIds || \"[]\"\n        );\n        this.keepLast = new KeepLast();\n    }\n\n    onSelectAllSpamClick() {\n        const inputEls = this.el.querySelectorAll(\".modal .tab-pane.active input\");\n        inputEls.forEach((el) => el.checked = true);\n    }\n\n    /**\n     * @param {InputEvent} ev\n     */\n    async onSpamSearchInput(ev) {\n        const toSearch = ev.target.value;\n        const posts = await this.waitFor(this.keepLast.add(\n            this.waitFor(this.services.orm.searchRead(\n                \"forum.post\",\n                [[\"id\", \"in\", this.spamIDs],\n                    \"|\",\n                [\"name\", \"ilike\", toSearch],\n                [\"content\", \"ilike\", toSearch]],\n                [\"name\", \"content\"]\n            ))\n        ));\n        const postSpamEl = this.el.querySelector(\"div.post_spam\");\n        this.removeChildren(postSpamEl);\n        if (!posts.length) {\n            return;\n        }\n        Object.values(posts).forEach((post) => {\n            const childEl = cloneContentEls(post.content).firstElementChild;\n            post.content = childEl.textContent.substring(0, 250);\n        });\n        // No need for cleanup, it's already done above.\n        this.renderAt(\"website_forum.spam_search_name\", { posts }, postSpamEl);\n    }\n\n    async onMarkSpamClick() {\n        const key = this.el.querySelector(\".modal .tab-pane.active\").dataset.key;\n        const inputEls = this.el.querySelectorAll(\".modal .tab-pane.active input.form-check-input:checked\");\n        const values = Array.from(inputEls).map((inputEl) => parseInt(inputEl.value));\n        await this.waitFor(this.services.orm.call(\"forum.post\", \"mark_as_offensive_batch\", [\n            this.spamIDs,\n            key,\n            values,\n        ]));\n        browser.location.reload();\n    }\n}\n\nregistry\n    .category(\"public.interactions\")\n    .add(\"website_forum.website_forum_spam\", WebsiteForumSpam);\n", "import { _t } from \"@web/core/l10n/translation\";\nimport {\n    registerBackendAndFrontendTour,\n} from '@website/js/tours/tour_utils';\nimport { stepUtils } from \"@web_tour/tour_utils\";\n\nregisterBackendAndFrontendTour(\"question\", {\n    url: '/forum/1',\n}, () => [{\n    trigger: \".o_wforum_ask_btn\",\n    tooltipPosition: \"left\",\n    content: _t(\"Create a new post in this forum by clicking on the button.\"),\n    run: \"click\",\n    expectUnloadPage: true,\n}, {\n    trigger: \"input[name=post_name]\",\n    tooltipPosition: \"top\",\n    content: _t(\"Give your post title.\"),\n    run: \"edit Test\",\n},\n{\n    trigger: `input[name=post_name]:not(:empty)`,\n},\n{\n    trigger: \".note-editable p\",\n    content: _t(\"Put your question here.\"),\n    tooltipPosition: \"bottom\",\n    run: \"editor Test\",\n},\n{\n    trigger: `.note-editable p:not(:text(<br>))`,\n},\n{\n    trigger: \".o_select_menu_toggler\",\n    content: _t(\"Insert tags related to your question.\"),\n    tooltipPosition: \"top\",\n    run: \"click\",\n},\n...stepUtils.editSelectMenuInput(\".o_select_menu_input\", \"Test\"),\n{\n    content: \"Select found select menu item\",\n    trigger: \".o_popover.o_select_menu_menu .o_select_menu_item:contains('Test')\",\n    run: 'click',\n},\n{\n    content: \"Close search bar\",\n    trigger: \"body\",\n    run: 'click',\n},\n{\n    trigger: \"button:contains(/^Post/)\",\n    content: _t(\"Click to post your question.\"),\n    tooltipPosition: \"bottom\",\n    run: \"click\",\n    expectUnloadPage: true,\n},\n{\n    trigger: \".o_wforum_content_wrapper .h3:contains(test)\",\n},\n{\n    isActive: [\"auto\"],\n    trigger: \".modal.modal_shown.show:contains(thanks for posting!) button.btn-close\",\n    run: \"click\",\n},\n{\n    trigger: \"a:contains(Reply).collapsed\",\n    content: _t(\"Click to reply.\"),\n    tooltipPosition: \"bottom\",\n    run: \"click\",\n},\n{\n    trigger: \".note-editable p\",\n    content: _t(\"Put your answer here.\"),\n    tooltipPosition: \"bottom\",\n    run: \"editor Test\",\n},\n{\n    trigger: `.note-editable p:not(:text(<br>))`,\n},\n{\n    trigger: \"button:contains(\\\"Post Answer\\\")\",\n    content: _t(\"Click to post your answer.\"),\n    tooltipPosition: \"bottom\",\n    run: \"click\",\n    expectUnloadPage: true,\n},\n{\n    trigger: \".o_wforum_content_wrapper .h3:contains(test)\",\n},\n{\n    isActive: [\"auto\"],\n    trigger: \".modal.modal_shown.show:contains(thanks for posting!) button.btn-close\",\n    run: \"click\",\n}, {\n    trigger: \".o_wforum_validate_toggler[data-karma]:first\",\n    content: _t(\"Click here to accept this answer.\"),\n    tooltipPosition: \"right\",\n    run: \"click\",\n}, {\n    content: \"Check edit button is there\",\n    trigger: \"a:contains('Edit your answer')\",\n}]);\n", "import { Component, useEffect } from \"@odoo/owl\";\nimport { useChildRef } from \"@web/core/utils/hooks\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\n\nexport class FlagMarkAsOffensiveDialog extends Component {\n    static template = \"website_forum.FlagMarkAsOffensiveDialog\";\n    static components = { Dialog };\n    static props = {\n        title: String,\n        body: String,\n        close: Function,\n    };\n\n    setup() {\n        this.modalRef = useChildRef();\n\n        const onClickDiscard = (ev) => {\n            ev.preventDefault();\n            this.props.close();\n        };\n\n        useEffect(\n            (discardButton) => {\n                if (discardButton) {\n                    discardButton.addEventListener(\"click\", onClickDiscard);\n                    return () => {\n                        discardButton.removeEventListener(\"click\", onClickDiscard);\n                    };\n                }\n            },\n            () => [this.modalRef.el?.querySelector(\".btn-link\")]\n        );\n    }\n}\n", "import { Component, useState, onWillStart } from \"@odoo/owl\";\nimport { get } from \"@web/core/network/http_service\";\nimport { SelectMenu } from \"@web/core/select_menu/select_menu\";\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\n\nexport class WebsiteForumTagsWrapper extends Component {\n    static template = \"website_forum.WebsiteForumTagsWrapper\";\n    static components = { SelectMenu, DropdownItem };\n    static defaultProps = {\n        isReadOnly: false,\n    };\n    static props = {\n        defaulValue: { optional: true, type: Array },\n        isReadOnly: { optional: true, Type: Boolean },\n    };\n\n    setup() {\n        this.state = useState({\n            value: this.props.defaulValue || [],\n        });\n        onWillStart(async () => {\n            await this.loadChoices();\n        });\n    }\n\n    showCreateOption(searchValue) {\n        // The \"Create\" option should not be visible if:\n        // 1. Tag length is less than 2.\n        // 2. The tag already exists (tags are created on form submission, so\n        // consider the current value).\n        // 3. There is insufficient karma.\n        const karma = document.querySelector(\"#karma\").value;\n        const editKarma = document.querySelector(\"#karma_edit_retag\").value;\n        const hasEnoughKarma = parseInt(karma) >= parseInt(editKarma);\n\n        return hasEnoughKarma && searchValue?.length >= 2\n            && !this.state.choices.some(c => c.label === searchValue)\n            && !this.state.value.some(v => v === `_${searchValue.trim()}`);\n    }\n\n    onCreateOption(string) {\n        const choices = string.split(\",\").map((c) => ({ label: c.trim(), value: `_${c.trim()}` }));\n        this.state.choices.push(...choices);\n        this.onSelect([...this.state.value, ...choices.map((c) => c.value)]);\n    }\n\n    onSelect(values) {\n        this.state.value = values;\n    }\n\n    async loadChoices(searchString = \"\") {\n        const forumID = document.querySelector(\"#wrapwrap\").dataset.forum_id;\n        const choices = await new Promise((resolve, reject) => {\n            get(`/forum/get_tags?query=${searchString}&limit=${50}&forum_id=${forumID}`).then(\n                (result) => {\n                    result.forEach((choiceEl) => {\n                        choiceEl.value = choiceEl.id;\n                        choiceEl.label = choiceEl.name;\n                    });\n                    resolve(result);\n                }\n            );\n        });\n        this.state.choices = choices;\n    }\n}\n", "import { useExternalListener, useRef } from \"@odoo/owl\";\n\n/**\n * @param {string} targetRefName\n * @param {number} [minHeight]\n * @returns {Function} event listener for t-on-mousedown\n */\nexport function useResizer(targetRefName, minHeight = 100) {\n    const targetRef = useRef(targetRefName);\n    let isMouseDownOnResizer = false;\n    let startOffsetTop, startHeight;\n    const onResizerMouseDown = (ev) => {\n        isMouseDownOnResizer = true;\n        startHeight = targetRef.el.offsetHeight;\n        startOffsetTop = ev.pageY;\n    };\n    useExternalListener(document, \"mousemove\", (ev) => {\n        if (isMouseDownOnResizer) {\n            const offsetTop = ev.pageY - startOffsetTop;\n            const newHeight = Math.max(startHeight + offsetTop, minHeight);\n            targetRef.el.style.height = `${newHeight}px`;\n        }\n    });\n    useExternalListener(document, \"mouseup\", () => (isMouseDownOnResizer = false));\n    return onResizerMouseDown;\n}\n", "import { removeClass } from \"@html_editor/utils/dom\";\nimport { markup, onMounted, useExternalListener } from \"@odoo/owl\";\nimport { BASIC_PLUGINS, FULL_EDIT_PLUGINS } from \"../../plugins/plugin_sets\";\nimport { useResizer } from \"./resizer_hook\";\nimport { Wysiwyg } from \"@html_editor/wysiwyg\";\n\nexport class WebsiteForumWysiwyg extends Wysiwyg {\n    static template = \"website_forum.WebsiteForumWysiwyg\";\n    static props = {\n        ...super.props,\n        textareaEl: HTMLElement,\n        fullEdit: Boolean,\n        getRecordInfo: Function,\n        resizable: { type: Boolean, optional: true },\n        height: { type: String, optional: true },\n    };\n    static defaultProps = {\n        ...super.defaultProps,\n        class: \"odoo-editor\",\n        contentClass: \"note-editable\",\n    };\n\n    /** @override */\n    setup() {\n        super.setup();\n        if (this.props.resizable) {\n            // Event listener added on template.\n            this.onResizerMouseDown = useResizer(\"content\");\n        }\n        const form = this.props.textareaEl.closest(\"form\");\n        // Prevent form submission behavior of buttons inside the form\n        onMounted(() =>\n            form.querySelectorAll(\".o-wysiwyg button\").forEach((btn) => (btn.type = \"button\"))\n        );\n        this.submitButton = form.querySelector(\"button[type=submit]\");\n        useExternalListener(this.submitButton, \"click\", this.onSubmitButtonClick.bind(this));\n        this.readyToSubmit = false;\n\n        const postReplyWrapper = form.closest(\"#post_reply\");\n        if (postReplyWrapper) {\n            const clearSelection = () =>\n                this.editor.shared.selection.setCursorStart(this.editor.editable);\n\n            // On post reply, the discard button simply hides the editable.\n            // Clear the selection to close any overlay dependent on an uncollapsed\n            // selection (like the toolbar).\n            const discardButton = postReplyWrapper.querySelector(\".o_wforum_discard_btn\");\n            useExternalListener(discardButton, \"click\", clearSelection);\n\n            // Expanding to full view changes the editable's position.\n            // Clear the selection to close overlays.\n            const toggleExpandButton = postReplyWrapper.querySelector(\".o_wforum_expand_toggle\");\n            useExternalListener(toggleExpandButton, \"click\", clearSelection);\n        }\n    }\n\n    /** @override */\n    getEditorConfig() {\n        return {\n            getRecordInfo: this.props.getRecordInfo,\n            Plugins: this.props.fullEdit ? FULL_EDIT_PLUGINS : BASIC_PLUGINS,\n            content: this.getTextAreaContent(),\n            resources: {\n                start_edition_handlers: () => this.cleanImageClasses(this.editor.editable),\n                clean_for_save_handlers: ({ root }) => this.cleanImageClasses(root),\n            },\n            defaultLinkAttributes: { rel: \"ugc\" },\n            dropImageAsAttachment: true,\n            allowImageTransform: this.props.fullEdit,\n            height: this.props.height,\n        };\n    }\n\n    cleanImageClasses(root) {\n        // float-start class messes up the post layout OPW 769721\n        const classNames = [\"o_we_selected_image\", \"float-start\"];\n        root.querySelectorAll(\"img\").forEach((img) => removeClass(img, ...classNames));\n    }\n\n    getTextAreaContent() {\n        const textarea = this.props.textareaEl;\n        let content = textarea.getAttribute(\"content\") || textarea.value || \"\";\n        content = DOMPurify.sanitize(content, { ADD_ATTR: [\"contenteditable\"] });\n        if (!content.trim()) {\n            content = \"<p><br></p>\";\n        }\n        return markup(content);\n    }\n\n    onSubmitButtonClick(ev) {\n        if (this.readyToSubmit) {\n            return;\n        }\n        ev.preventDefault();\n        this.editor.shared.imageSave.savePendingImages().finally(() => {\n            this.props.textareaEl.value = this.editor.getContent();\n            this.readyToSubmit = true;\n            this.submitButton.click();\n        });\n    }\n}\n", "import { FontPlugin } from \"@html_editor/main/font/font_plugin\";\n\nconst excludedPowerboxCommands = [\"setTagHeading1\", \"setTagHeading2\", \"setTagHeading3\"];\nconst excludedFontItems = [\"h1\", \"h2\", \"h3\"];\n\nexport class ForumFontPlugin extends FontPlugin {\n    resources = {\n        ...this.resources,\n        powerbox_items: this.resources.powerbox_items.filter(\n            (item) => !excludedPowerboxCommands.includes(item.commandId)\n        ),\n        // Remove font-size selector from toolbar\n        toolbar_items: this.resources.toolbar_items.filter(\n            (item) => item.object.id !== \"font-size\"\n        ),\n        font_items: this.resources.font_items.filter(\n            (item) => !excludedFontItems.includes(item.object.tagName)\n        ),\n    };\n}\n", "import { HistoryPlugin } from \"@html_editor/core/history_plugin\";\nimport { withSequence } from \"@html_editor/utils/resource\";\n\nexport class ForumHistoryPlugin extends HistoryPlugin {\n    resources = {\n        ...this.resources,\n        // Undo and redo toolbar buttons are always available\n        toolbar_groups: withSequence(5, { id: \"history\", namespaces: [\"compact\", \"expanded\"] }),\n        toolbar_items: [\n            {\n                id: \"undo\",\n                groupId: \"history\",\n                commandId: \"historyUndo\",\n                isDisabled: () => !this.canUndo(),\n            },\n            {\n                id: \"redo\",\n                groupId: \"history\",\n                commandId: \"historyRedo\",\n                isDisabled: () => !this.canRedo(),\n            },\n        ],\n    };\n}\n", "import { MAIN_PLUGINS } from \"@html_editor/plugin_sets\";\nimport { ForumFontPlugin } from \"./font_plugin\";\nimport { ForumHistoryPlugin } from \"./history_plugin\";\n\nconst removedPlugins = new Set([\"colorUi\", \"iconColor\"]);\n\nconst customPlugins = {\n    font: ForumFontPlugin,\n    history: ForumHistoryPlugin,\n};\n\nexport const FULL_EDIT_PLUGINS = MAIN_PLUGINS.filter((P) => !removedPlugins.has(P.id)).map(\n    (P) => customPlugins[P.id] || P\n);\n\nconst fullEditOnlyPlugins = new Set([\"link\", \"linkPaste\", \"mediaUrlPaste\", \"imageCrop\", \"media\"]);\n\nexport const BASIC_PLUGINS = FULL_EDIT_PLUGINS.filter((P) => !fullEditOnlyPlugins.has(P.id));\n", "\nimport { patch } from '@web/core/utils/patch';\nimport { patchDynamicContent } from '@web/public/utils';\nimport { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc } from '@web/core/network/rpc';\nimport { ExpressCheckout } from '@payment/interactions/express_checkout';\nimport paymentDemoMixin from '@payment_demo/interactions/payment_demo_mixin';\n\npatch(ExpressCheckout.prototype, {\n    setup() {\n        super.setup();\n        patchDynamicContent(this.dynamicContent, {\n            'button[name=\"o_payment_submit_button\"]': {\n                't-on-click.stop.prevent': this.debounced(\n                    this.initiateExpressPayment.bind(this), 500, true\n                ),\n            },\n        });\n        document.querySelector('[name=\"o_payment_submit_button\"]')?.removeAttribute('disabled');\n    },\n\n    /**\n     * Process the payment.\n     *\n     * @param {Event} ev\n     * @return {void}\n     */\n    async initiateExpressPayment(ev) {\n        const providerId = ev.target.parentElement.dataset.providerId;\n        let expressDeliveryAddress = {};\n        if (this.paymentContext.shippingInfoRequired) {\n            const shippingInfo = document.querySelector(\n                `#o_payment_demo_shipping_info_${providerId}`\n            );\n            expressDeliveryAddress = {\n                'name': shippingInfo.querySelector('#o_payment_demo_shipping_name').value,\n                'email': shippingInfo.querySelector('#o_payment_demo_shipping_email').value,\n                'street': shippingInfo.querySelector('#o_payment_demo_shipping_address').value,\n                'street2': shippingInfo.querySelector('#o_payment_demo_shipping_address2').value,\n                'zip': shippingInfo.querySelector('#o_payment_demo_shipping_zip').value,\n                'city': shippingInfo.querySelector('#o_payment_demo_shipping_city').value,\n                'country': shippingInfo.querySelector('#o_payment_demo_shipping_country').value,\n            };\n            // Call the shipping address update route to fetch the shipping options.\n            const { delivery_methods } = await this.waitFor(rpc(\n                this.paymentContext['shippingAddressUpdateRoute'],\n                {partial_delivery_address: expressDeliveryAddress},\n            ));\n            if (delivery_methods.length > 0) {\n                const id = parseInt(delivery_methods[0].id);\n                await this.waitFor(rpc('/shop/set_delivery_method', {dm_id: id}));\n            } else {\n                this.services.dialog.add(ConfirmationDialog, {\n                    title: _t(\"Validation Error\"),\n                    body: _t(\"No delivery method is available.\"),\n                });\n                return;\n            }\n        }\n        await this.waitFor(rpc(\n            document.querySelector(\n                '[name=\"o_payment_express_checkout_form\"]'\n            ).dataset['expressCheckoutRoute'],\n            {\n                'shipping_address': expressDeliveryAddress,\n                'billing_address': {\n                    'name': 'Demo User',\n                    'email': 'demo@test.com',\n                    'street': 'Rue des Bourlottes 9',\n                    'street2': '23',\n                    'country': 'BE',\n                    'city':'Ramillies',\n                    'zip':'1367',\n                },\n            }\n        ));\n        const processingValues = await this.waitFor(rpc(\n            this.paymentContext['transactionRoute'],\n            this._prepareTransactionRouteParams(providerId),\n        ));\n        paymentDemoMixin.processDemoPayment(processingValues);\n    },\n});\n", "import { _t } from \"@web/core/l10n/translation\";\nimport { rpc, RPCError } from \"@web/core/network/rpc\";\n\nexport default {\n\n    /**\n     * Simulate a feedback from a payment provider and redirect the customer to the status page.\n     *\n     * @private\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n    async processDemoPayment(processingValues) {\n        const customerInput = document.getElementById('customer_input').value;\n        const simulatedPaymentState = document.getElementById('simulated_payment_state').value;\n\n        rpc('/payment/demo/simulate_payment', {\n            'reference': processingValues.reference,\n            'payment_details': customerInput,\n            'simulated_state': simulatedPaymentState,\n        }).then(() => {\n            window.location = '/payment/status';\n        }).catch(error => {\n            if (error instanceof RPCError) {\n                // These methods don't exist in Express Checkout.\n                this._displayErrorDialog?.(_t(\"Payment processing failed\"), error.data.message);\n                this._enableButton?.();\n            } else {\n                return Promise.reject(error);\n            }\n        });\n    },\n\n};\n", "import { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\nimport paymentDemoMixin from '@payment_demo/interactions/payment_demo_mixin';\n\npatch(PaymentForm.prototype, {\n\n    // #=== DOM MANIPULATION ===#\n\n    /**\n     * Prepare the inline form of Demo for direct payment.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {number} providerId - The id of the selected payment option's provider.\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The online payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (providerCode !== 'demo') {\n            await super._prepareInlineForm(...arguments);\n            return;\n        } else if (flow === 'token') {\n            return;\n        }\n        this._setPaymentFlow('direct');\n    },\n\n    // #=== PAYMENT FLOW ===#\n\n    /**\n     * Simulate a feedback from a payment provider and redirect the customer to the status page.\n     *\n     * @override method from payment.payment_form\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n    async _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {\n        if (providerCode !== 'demo') {\n            await super._processDirectFlow(...arguments);\n            return;\n        }\n        paymentDemoMixin.processDemoPayment(processingValues);\n    },\n\n});\n", "import { patch } from '@web/core/utils/patch';\nimport { PaymentButton } from '@payment/interactions/payment_button';\n\npatch(PaymentButton.prototype, {\n\n    /**\n     * Hide the disabled PayPal buttons and show the enabled ones.\n     *\n     * @override method from @payment/interactions/payment_button\n     * @private\n     * @return {void}\n     */\n    _setEnabled() {\n        if (!this.paymentButton.dataset.isPaypal) {\n            super._setEnabled();\n            return;\n        }\n\n        const paypalButtons = document.querySelectorAll(\n            '[id^=\"o_paypal_disabled_button\"], [id^=\"o_paypal_enabled_button\"]'\n        );\n        paypalButtons.forEach(button => {\n            const action = button.id.startsWith('o_paypal_disabled_button') ? 'add' : 'remove';\n            button.classList[action]('d-none');\n        });\n    },\n\n    /**\n     * Hide the enabled PayPal buttons and show the disabled ones.\n     *\n     * @override method from @payment/interactions/payment_button\n     * @private\n     * @return {void}\n     */\n    _disable() {\n        if (!this.paymentButton.dataset.isPaypal) {\n            super._disable();\n            return;\n        }\n\n        const paypalButtons = document.querySelectorAll(\n            '[id^=\"o_paypal_disabled_button\"], [id^=\"o_paypal_enabled_button\"]'\n        );\n        paypalButtons.forEach(button => {\n            const action = button.id.startsWith('o_paypal_enabled_button') ? 'add' : 'remove';\n            button.classList[action]('d-none');\n        });\n    },\n\n    /**\n     * Disable the generic behavior that would hide the PayPal button container.\n     *\n     * @override method from @payment/interactions/payment_button\n     * @private\n     * @return {void}\n     */\n    _hide() {\n        if (!this.paymentButton.dataset.isPaypal) {\n            super._hide();\n        }\n    },\n\n    /**\n     * Disable the generic behavior that would show the PayPal button container.\n     *\n     * @override method from @payment/interactions/payment_button\n     * @private\n     * @return {void}\n     */\n    _show() {\n        if (!this.paymentButton.dataset.isPaypal) {\n            super._show();\n        }\n    },\n\n});\n", "/* global paypal */\n\nimport { loadJS } from '@web/core/assets';\nimport { _t } from '@web/core/l10n/translation';\nimport { rpc, RPCError } from '@web/core/network/rpc';\nimport { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n\n    setup() {\n        super.setup();\n        this.paypalData = {}; // Store the component of each instantiated payment method.\n        this.selectedOptionId = undefined;\n    },\n\n    // #=== DOM MANIPULATION ===#\n\n    /**\n     * @override\n     */\n    async willStart() {\n        // Suffix the button IDs to prevent collisions when multiple button containers are present.\n        const paypalEnabledButtons = [...document.querySelectorAll('#o_paypal_enabled_button')];\n        const paypalDisabledButtons = [...document.querySelectorAll('#o_paypal_disabled_button')];\n        paypalEnabledButtons.forEach((button, index) => button.id += `_${index}`);\n        paypalDisabledButtons.forEach((button, index) => button.id += `_${index}`);\n\n        await super.willStart(...arguments);\n    },\n\n    /**\n     * Hides paypal button container if the expanded inline form is another provider.\n     *\n     * @private\n     * @param {HTMLInputElement} radio - The radio button linked to the payment option.\n     * @return {void}\n     */\n    async _expandInlineForm(radio) {\n        const providerCode = this._getProviderCode(radio);\n        if (providerCode !== 'paypal') {\n            for (const buttonContainer of document.querySelectorAll('#o_paypal_button_container')) {\n                buttonContainer.classList.add('d-none');\n            }\n        }\n        await super._expandInlineForm(...arguments);\n    },\n\n    /**\n     * Prepare the inline form of Paypal for direct payment.\n     *\n     * The PayPal SDK creates payment buttons based on the client_id and the currency of the order.\n     *\n     * Two payment buttons are created for each button container: one enabled and one disabled. The\n     * enabled button is shown when the user is allowed to click on it, and the disabled button is\n     * shown otherwise. This trick is necessary as the PayPal SDK does not provide a way to disable\n     * the button after it has been created.\n     *\n     * The created buttons are saved and reused when switching between different payment methods to\n     * avoid recreating the buttons.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {number} providerId - The id of the selected payment option's provider.\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The online payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (providerCode !== 'paypal') {\n            await super._prepareInlineForm(...arguments);\n            return;\n        }\n\n        this._hideInputs();\n        this._setPaymentFlow('direct');\n        const paypalLoadingList = document.querySelectorAll('#o_paypal_loading');\n        for (const paypalLoading of paypalLoadingList) {\n            paypalLoading.classList.remove('d-none');\n        }\n\n        // Check if instantiation of the component is needed.\n        if (this.selectedOptionId && this.selectedOptionId !== paymentOptionId) {\n            Object.entries(this.paypalData).forEach(([_key, value]) => {\n                value.enabledButtons.forEach(btn => btn.hide());\n                value.disabledButtons.forEach(btn => btn.hide());\n            });\n        }\n        const currentPayPalData = this.paypalData[paymentOptionId];\n        if (currentPayPalData && this.selectedOptionId !== paymentOptionId) {\n            const paypalSDKURL = this.paypalData[paymentOptionId]['sdkURL']\n            await this.waitFor(this._paypalLoadSDK(paypalSDKURL));\n            this.paypalData[this.selectedOptionId]['enabledButtons'].forEach(btn => btn.show());\n            this.paypalData[this.selectedOptionId]['disabledButtons'].forEach(btn => btn.show());\n        }\n        else if (!currentPayPalData) {\n            this.paypalData[paymentOptionId] = {};\n            const radio = document.querySelector('input[name=\"o_payment_radio\"]:checked');\n            let inlineFormValues;\n            let paypalColor = 'blue';\n            if (radio) {\n                inlineFormValues = JSON.parse(radio.dataset['paypalInlineFormValues']);\n                paypalColor = radio.dataset['paypalColor'];\n            }\n\n            // https://developer.paypal.com/sdk/js/configuration/#link-queryparameters\n            const { client_id, currency_code } = inlineFormValues;\n            const paypalSDKURL = `https://www.paypal.com/sdk/js?client-id=${\n                client_id}&components=buttons&currency=${currency_code}&intent=capture`;\n            this.paypalData[paymentOptionId]['sdkURL'] = paypalSDKURL;\n            await this.waitFor(this._paypalLoadSDK(paypalSDKURL));\n\n            // Create the two sets of PayPal buttons.\n            // See https://developer.paypal.com/sdk/js/reference.\n            this.paypalData[paymentOptionId]['enabledButtons'] = [];\n            document.querySelectorAll('[id^=\"o_paypal_enabled_button\"]').forEach(domButton => {\n                const enabledButton = paypal.Buttons({\n                    fundingSource: paypal.FUNDING.PAYPAL,\n                    style: { // https://developer.paypal.com/sdk/js/reference/#link-style\n                        color: paypalColor,\n                        label: 'paypal',\n                        disableMaxWidth: true,\n                        borderRadius: 6,\n                    },\n                    createOrder: this._paypalOnClick.bind(this),\n                    onApprove: this._paypalOnApprove.bind(this),\n                    onCancel: this._paypalOnCancel.bind(this),\n                    onError: this._paypalOnError.bind(this),\n                });\n                enabledButton.render(`#${domButton.id}`);\n                this.paypalData[paymentOptionId]['enabledButtons'].push(enabledButton);\n            });\n\n            this.paypalData[paymentOptionId]['disabledButtons'] = [];\n            document.querySelectorAll('[id^=\"o_paypal_disabled_button\"]').forEach(domButton => {\n                const disabledButton = paypal.Buttons({\n                    fundingSource: paypal.FUNDING.PAYPAL,\n                    style: { // https://developer.paypal.com/sdk/js/reference/#link-style\n                        color: 'silver',\n                        label: 'paypal',\n                        disableMaxWidth: true,\n                        borderRadius: 6,\n                    },\n                    onInit: (data, actions) => actions.disable(),  // Permanently disable the button.\n                });\n                disabledButton.render(`#${domButton.id}`);\n                this.paypalData[paymentOptionId]['disabledButtons'].push(disabledButton);\n            });\n        }\n\n        for (const paypalLoading of paypalLoadingList) {\n            paypalLoading.classList.add('d-none');\n        }\n        for (const buttonContainer of document.querySelectorAll('#o_paypal_button_container')) {\n            buttonContainer.classList.remove('d-none');\n        }\n        this.selectedOptionId = paymentOptionId;\n    },\n\n    /**\n     * Load the JS from the PayPal SDK URL and set an identifier dedicated to Odoo, for PayPal to be\n     * able to recognize which transactions are originating from Odoo.\n     *\n     * @private\n     * @param {string} paypalSDKURL - The SDK URL that needs to be loaded on the page.\n     * @return {void}\n     */\n    async _paypalLoadSDK(paypalSDKURL) {\n        await loadJS(paypalSDKURL);\n        const paypalSDKs = document.querySelectorAll(`script[src=\"${paypalSDKURL}\"]`);\n        [...paypalSDKs].forEach(sdk => {\n            sdk.setAttribute('data-partner-attribution-id', 'OdooInc_SP_EC');\n        });\n    },\n\n    // #=== PAYMENT FLOW ===#\n\n    /**\n     * Handle the click event of the component and initiate the payment.\n     *\n     * @private\n     * @return {void}\n     */\n    async _paypalOnClick() {\n        await this.waitFor(this.submitForm(new Event(\"PayPalClickEvent\")));\n        return this.paypalData[this.selectedOptionId].paypalOrderId;\n    },\n\n    _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {\n        if (providerCode !== 'paypal') {\n            super._processDirectFlow(...arguments);\n            return;\n        }\n        this.paypalData[paymentOptionId].paypalOrderId = processingValues['order_id'];\n        this.paypalData[paymentOptionId].paypalTxRef = processingValues['reference'];\n    },\n\n    /**\n     * Handle the approval event of the component and complete the payment.\n     *\n     * @private\n     * @param {object} data - The data returned by PayPal on approving the order.\n     * @return {void}\n     */\n    async _paypalOnApprove(data) {\n        const orderID = data.orderID;\n        try {\n            await this.waitFor(rpc('/payment/paypal/complete_order', {\n                'order_id': orderID,\n                'reference': this.paypalData[this.selectedOptionId].paypalTxRef,\n            }));\n            // Close the PayPal buttons that were rendered\n            for (const enabledButton of this.paypalData[this.selectedOptionId]['enabledButtons']) {\n                enabledButton.close();\n            }\n            window.location = '/payment/status';\n        } catch (error) {\n            if (error instanceof RPCError) {\n                this._displayErrorDialog(_t(\"Payment processing failed\"), error.data.message);\n                this._enableButton(); // The button has been disabled before initiating the flow.\n            }\n            return Promise.reject(error);\n        }\n    },\n\n    /**\n     * Handle the cancel event of the component.\n     * @private\n     * @return {void}\n     */\n    _paypalOnCancel() {\n        this._enableButton();\n    },\n\n    /**\n     * Handle the error event of the component.\n     * @private\n     * @param {object} error - The error in the component.\n     * @return {void}\n     */\n    _paypalOnError(error) {\n        const message = error.message;\n        this._enableButton();\n        // Paypal throws an error if the popup is closed before it can load;\n        // this case should be treated as an onCancel event.\n        if (message !== \"Detected popup close\" && !(error instanceof RPCError)) {\n            this._displayErrorDialog(_t(\"Payment processing failed\"), message);\n        }\n    },\n});\n", "import { _t } from '@web/core/l10n/translation';\nimport { rpc, RPCError } from '@web/core/network/rpc';\nimport { patch } from '@web/core/utils/patch';\n\nimport { PaymentForm } from '@payment/interactions/payment_form';\n\npatch(PaymentForm.prototype, {\n\n    // #=== DOM MANIPULATION ===#\n\n    /**\n     * Prepare the inline form of SEPA for direct payment.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {number} providerId - The id of the selected payment option's provider.\n     * @param {string} providerCode - The custom mode of the selected payment option's provider. The\n     *                                provider code is replaced in the payment form to allow\n     *                                comparing custom modes.\n     * @param {number} paymentOptionId - The id of the selected payment option\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The online payment flow of the selected payment option\n     * @return {void}\n     */\n    async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (providerCode !== 'sepa_direct_debit') {\n            await super._prepareInlineForm(...arguments);\n            return;\n        } else if (flow === 'token') {\n            return; // Don't show the form for tokens.\n        }\n        this._setPaymentFlow('direct');\n    },\n\n    // #=== PAYMENT FLOW ===#\n\n    /**\n     * Verify the validity of the IBAN input before trying to process a payment.\n     *\n     * @override method from @payment/js/payment_form\n     * @private\n     * @param {string} providerCode - The custom mode of the selected payment option's provider. The\n     *                                provider code is replaced in the payment form to allow\n     *                                comparing custom modes.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {string} flow - The payment flow of the selected payment option.\n     * @return {void}\n     */\n    async _initiatePaymentFlow(providerCode, paymentOptionId, paymentMethodCode, flow) {\n        if (providerCode !== 'sepa_direct_debit' || flow === 'token') {\n            // Tokens are handled by the generic flow.\n            await super._initiatePaymentFlow(...arguments);\n            return;\n        }\n\n        const ibanInput = this._getIbanInput();\n\n        if (!ibanInput.reportValidity()) {\n            this._enableButton();\n            return; // Let the browser request to fill out required fields\n        }\n\n        await super._initiatePaymentFlow(...arguments);\n    },\n\n    /**\n     * Link the IBAN to the transaction as an inactive mandate.\n     *\n     * @override method from payment.payment_form\n     * @private\n     * @param {string} providerCode - The code of the selected payment option's provider.\n     * @param {number} paymentOptionId - The id of the selected payment option.\n     * @param {string} paymentMethodCode - The code of the selected payment method, if any.\n     * @param {object} processingValues - The processing values of the transaction.\n     * @return {void}\n     */\n    async _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {\n        if (providerCode !== 'sepa_direct_debit') {\n            await super._processDirectFlow(...arguments);\n            return;\n        }\n\n        // Assign the SDD mandate corresponding to the IBAN to the transaction.\n        const ibanInput = this._getIbanInput();\n        try {\n            await this.waitFor(rpc('/payment/sepa_direct_debit/set_mandate', {\n                'reference': processingValues.reference,\n                'iban': ibanInput.value,\n                'access_token': processingValues.access_token,\n            }));\n            window.location = '/payment/status';\n        } catch (error) {\n            if (error instanceof RPCError) {\n                this._displayErrorDialog(_t(\"Payment processing failed\"), error.data.message);\n                this._enableButton();\n            } else {\n                return Promise.reject(error);\n            }\n        }\n    },\n\n    // #=== GETTERS ===#\n\n    /**\n     * Return the IBAN input.\n     *\n     * @private\n     * @return {HTMLInputElement}\n     */\n    _getIbanInput() {\n        const radio = document.querySelector('input[name=\"o_payment_radio\"]:checked');\n        const inlineForm = this._getInlineForm(radio);\n        return inlineForm?.querySelector('#o_sdd_iban');\n    },\n\n});\n", "import { Interaction } from \"@web/public/interaction\";\nimport { registry } from \"@web/core/registry\";\n\nexport class RippleEffect extends Interaction {\n    static selector = \".btn, .dropdown-toggle, .dropdown-item\";\n    dynamicContent = {\n        _root: {\n            \"t-on-click\": this.onClick,\n            \"t-att-class\": () => ({\n                o_js_ripple_effect: this.isActive,\n            }),\n        },\n    };\n    duration = 350;\n\n    setup() {\n        this.isActive = false;\n        this.rippleEl = undefined;\n        this.timeoutID = null;\n    }\n\n    /**\n     * @param {MouseEvent} ev\n     */\n    onClick(ev) {\n        if (!this.rippleEl) {\n            this.rippleEl = document.createElement(\"span\");\n            this.rippleEl.classList.add(\"o_ripple_item\");\n            this.rippleEl.style.animationDuration = `${this.duration}ms`;\n            this.insert(this.rippleEl, this.el);\n        }\n\n        clearTimeout(this.timeoutID);\n        if (this.isActive) {\n            this.isActive = false;\n            this.updateContent();\n        }\n\n        const rect = this.el.getBoundingClientRect();\n        const offsetY = rect.top + window.scrollY;\n        const offsetX = rect.left + window.scrollX;\n        // The diameter need to be recomputed because a change of window width\n        // can affect the size of a button (e.g. media queries).\n        const diameter = Math.max(this.el.clientWidth, this.el.clientHeight);\n\n        this.rippleEl.style.width = `${diameter}px`;\n        this.rippleEl.style.height = `${diameter}px`;\n        this.rippleEl.style.top = `${ev.pageY - offsetY - diameter / 2}px`;\n        this.rippleEl.style.left = `${ev.pageX - offsetX - diameter / 2}px`;\n\n        this.isActive = true;\n        this.timeoutID = this.waitForTimeout(() => {\n            this.isActive = false;\n            this.rippleEl?.remove();\n            this.rippleEl = undefined;\n        }, this.duration);\n    }\n}\n\nregistry.category(\"public.interactions\").add(\"website.ripple_effect\", RippleEffect);\n"], "file": "/web/assets/1/9c94870/web.assets_frontend_lazy.js", "sourceRoot": "../../../../"}